diff --git a/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java b/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java
index 4d006ba65b0..fc44a26cdbd 100644
--- a/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java
+++ b/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java
@@ -240,6 +240,7 @@ public class ModelDescriptionConstants {
public static final String HTTP_UPGRADE = "http-upgrade";
public static final String HTTP_UPGRADE_ENABLED = "http-upgrade-enabled";
public static final String HTTP_INTERFACE = "http-interface";
+ public static final String ID = "id";
public static final String IDENTITY = "identity";
public static final String IGNORED = "ignored-by-unaffected-host-controller";
public static final String IGNORED_RESOURCES = "ignored-resources";
@@ -275,6 +276,7 @@ public class ModelDescriptionConstants {
public static final String LEVEL = "level";
public static final String LDAP = "ldap";
public static final String LDAP_CONNECTION = "ldap-connection";
+ public static final String LIST_SNAPSHOTS_OPERATION = "list-snapshots";
public static final String LOCAL = "local";
public static final String LOCAL_DESTINATION_OUTBOUND_SOCKET_BINDING = "local-destination-outbound-socket-binding";
public static final String LOCAL_HOST_NAME = "local-host-name";
@@ -359,10 +361,10 @@ public class ModelDescriptionConstants {
public static final String PASSWORD = "password";
public static final String PATH = "path";
public static final String PATHS = "paths";
+ public static final String PATTERN = "pattern";
public static final String PERIODIC_ROTATING_FILE_HANDLER = "periodic-rotating-file-handler";
public static final String PERMISSION_COMBINATION_POLICY = "permission-combination-policy";
public static final String PERSIST_NAME = "persist-name";
- public static final String PATTERN = "pattern";
public static final String PERSISTENT = "persistent";
public static final String PLAIN_TEXT = "plain-text";
public static final String PLATFORM_MBEAN = "platform-mbean";
@@ -549,6 +551,7 @@ public class ModelDescriptionConstants {
*/
public static final String SYNC_REMOVED_FOR_READD = "sync-dropped-for-readd";
public static final String TAIL_COMMENT_ALLOWED = "tail-comment-allowed";
+ public static final String TAKE_SNAPSHOT_OPERATION = "take-snapshot";
public static final String TARGET_PATH = "target-path";
public static final String TCP = "tcp";
public static final String TIMEOUT = "timeout";
diff --git a/testsuite/domain/src/test/java/org/jboss/as/test/integration/domain/management/ReadConfigAsFeaturesDomainTestCase.java b/testsuite/domain/src/test/java/org/jboss/as/test/integration/domain/management/ReadConfigAsFeaturesDomainTestCase.java
new file mode 100644
index 00000000000..0f71a47996a
--- /dev/null
+++ b/testsuite/domain/src/test/java/org/jboss/as/test/integration/domain/management/ReadConfigAsFeaturesDomainTestCase.java
@@ -0,0 +1,209 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2018, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.as.test.integration.domain.management;
+
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.operations.common.Util;
+import org.jboss.as.test.integration.domain.management.util.DomainLifecycleUtil;
+import org.jboss.as.test.integration.domain.management.util.DomainTestSupport;
+import org.jboss.as.test.integration.management.api.ReadConfigAsFeaturesTestBase;
+import org.jboss.dmr.ModelNode;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.*;
+
+/**
+ * Tests {@code read-config-as-features} operation in domain mode.
+ *
+ * @author Richard Janík
+ */
+public class ReadConfigAsFeaturesDomainTestCase extends ReadConfigAsFeaturesTestBase {
+
+ private static DomainTestSupport testSupport;
+ private static DomainLifecycleUtil domainMasterLifecycleUtil;
+
+ private String defaultDomainConfig;
+ private String defaultHostConfig;
+ private ModelNode defaultDomainConfigAsFeatures;
+ private ModelNode defaultHostConfigAsFeatures;
+
+ @BeforeClass
+ public static void setupDomain() {
+ testSupport = DomainTestSupport.createAndStartSupport(DomainTestSupport.Configuration.create(ReadConfigAsFeaturesDomainTestCase.class.getSimpleName(),
+ "domain-configs/domain-standard.xml", "host-configs/host-master.xml", "host-configs/host-slave.xml"));
+ domainMasterLifecycleUtil = testSupport.getDomainMasterLifecycleUtil();
+ }
+
+ @AfterClass
+ public static void tearDownDomain() {
+ testSupport.stop();
+ testSupport = null;
+ domainMasterLifecycleUtil = null;
+ }
+
+ @Test
+ public void domainSystemPropertyTest() {
+ ModelNode redefineProperty = Util.getWriteAttributeOperation(PathAddress.pathAddress(SYSTEM_PROPERTY, "jboss.domain.test.property.one"), VALUE, "SIX");
+ ModelNode addProperty = Util.createAddOperation(PathAddress.pathAddress(SYSTEM_PROPERTY, "customProp"));
+ addProperty.get(BOOT_TIME).set(false);
+ addProperty.get(VALUE).set("customPropVal");
+
+ ModelNode expectedDomainConfigAsFeatures = defaultDomainConfigAsFeatures.clone();
+
+ // modify the existing property
+ ModelNode propertyId = new ModelNode();
+ propertyId.get(SYSTEM_PROPERTY).set("jboss.domain.test.property.one");
+ ModelNode existingProperty = getListElement(expectedDomainConfigAsFeatures, "domain.system-property", propertyId);
+ existingProperty.get(PARAMS).get(VALUE).set("SIX");
+
+ // add the new property
+ ModelNode newPropertyId = new ModelNode();
+ ModelNode newPropertyParams = new ModelNode();
+ ModelNode newProperty = new ModelNode();
+ newPropertyId.get(SYSTEM_PROPERTY).set("customProp");
+ newPropertyParams.get(BOOT_TIME).set(false);
+ newPropertyParams.get(VALUE).set("customPropVal");
+ newProperty.get(SPEC).set("domain.system-property");
+ newProperty.get(ID).set(newPropertyId);
+ newProperty.get(PARAMS).set(newPropertyParams);
+ // add the new property
+ expectedDomainConfigAsFeatures.add(newProperty);
+
+ doTest(Arrays.asList(redefineProperty, addProperty), expectedDomainConfigAsFeatures, PathAddress.EMPTY_ADDRESS);
+ }
+
+ @Test
+ public void domainProfileTest() {
+ ModelNode redefineProfileAttribute = Util.getWriteAttributeOperation(
+ PathAddress.pathAddress(PROFILE, DEFAULT).append(SUBSYSTEM, "io").append("buffer-pool", "default"),
+ "buffer-size", 500);
+ ModelNode removeSubsystemFromProfile = Util.createRemoveOperation(PathAddress.pathAddress(PROFILE, DEFAULT).append(SUBSYSTEM, "request-controller"));
+
+ ModelNode expectedDomainConfigAsFeatures = defaultDomainConfigAsFeatures.clone();
+
+ // remove the request controller subsystem
+ ModelNode defaultProfileId = new ModelNode();
+ defaultProfileId.get(PROFILE).set(DEFAULT);
+ ModelNode defaultProfile = getListElement(expectedDomainConfigAsFeatures, PROFILE, defaultProfileId);
+ int requestControllerSubsystemIndex = getFeatureNodeChildIndex(defaultProfile, "profile.subsystem.request-controller");
+ defaultProfile.get(CHILDREN).remove(requestControllerSubsystemIndex);
+
+ // rewrite the buffer-pool attribute
+ ModelNode ioSubsystem = getFeatureNodeChild(defaultProfile, "profile.subsystem.io");
+ ModelNode bufferPool = getFeatureNodeChild(ioSubsystem, "profile.subsystem.io.buffer-pool");
+ ModelNode bufferPoolParams = new ModelNode();
+ bufferPoolParams.get("buffer-size").set(500);
+ bufferPool.get(PARAMS).set(bufferPoolParams);
+
+ doTest(Arrays.asList(redefineProfileAttribute, removeSubsystemFromProfile), expectedDomainConfigAsFeatures, PathAddress.EMPTY_ADDRESS);
+ }
+
+ @Test
+ public void hostInterfaceTest() {
+ ModelNode redefineInterface = Util.getWriteAttributeOperation(PathAddress.pathAddress(HOST, MASTER).append(INTERFACE, "management"), INET_ADDRESS, "10.10.10.10");
+
+ ModelNode expectedHostConfigAsFeatures = defaultHostConfigAsFeatures.clone();
+
+ ModelNode managementInterfaceId = new ModelNode();
+ managementInterfaceId.get(INTERFACE).set("management");
+ ModelNode managementInterface = getFeatureNodeChild(expectedHostConfigAsFeatures.get(0), "host.interface", managementInterfaceId);
+ ModelNode managementInterfaceParams = new ModelNode();
+ managementInterfaceParams.get(INET_ADDRESS).set("10.10.10.10");
+ managementInterface.get(PARAMS).set(managementInterfaceParams);
+
+ doTest(Collections.singletonList(redefineInterface), expectedHostConfigAsFeatures, PathAddress.pathAddress(HOST, MASTER));
+ }
+
+ @Test
+ public void hostSubsystemTest() {
+ ModelNode redefineJmxAttribute = Util.getWriteAttributeOperation(
+ PathAddress.pathAddress(HOST, MASTER).append(SUBSYSTEM, "jmx").append("expose-model", "resolved"),
+ "domain-name", "customDomainName");
+
+ ModelNode expectedHostConfigAsFeatures = defaultHostConfigAsFeatures.clone();
+
+ ModelNode jmxSubsystem = getFeatureNodeChild(expectedHostConfigAsFeatures.get(0), "host.subsystem.jmx");
+ ModelNode exposeModelResolved = getFeatureNodeChild(jmxSubsystem, "host.subsystem.jmx.expose-model.resolved");
+ ModelNode exposeModelResolvedParams = new ModelNode();
+ exposeModelResolvedParams.get("domain-name").set("customDomainName");
+ exposeModelResolved.get(PARAMS).set(exposeModelResolvedParams);
+
+ doTest(Collections.singletonList(redefineJmxAttribute), expectedHostConfigAsFeatures, PathAddress.pathAddress(HOST, MASTER));
+ }
+
+ private void doTest(List operations, ModelNode expectedConfigAsFeatures, PathAddress domainOrHostPath) {
+ for (ModelNode operation : operations) {
+ domainMasterLifecycleUtil.executeForResult(operation);
+ }
+ if (!equalsWithoutListOrder(expectedConfigAsFeatures, getConfigAsFeatures(domainOrHostPath))) {
+ System.out.println("Actual:\n" + getConfigAsFeatures(domainOrHostPath).toJSONString(false) + "\nExpected:\n" + expectedConfigAsFeatures.toJSONString(false));
+ Assert.fail("There are differences between the expected and the actual model, see the test output for details");
+ }
+ }
+
+ @Override
+ protected void saveDefaultConfig() {
+ if (defaultDomainConfig == null || defaultHostConfig == null) {
+ ModelNode takeSnapshotOnDomain = Util.createEmptyOperation(TAKE_SNAPSHOT_OPERATION, PathAddress.EMPTY_ADDRESS);
+ ModelNode takeSnapshotOnHost = Util.createEmptyOperation(TAKE_SNAPSHOT_OPERATION, PathAddress.pathAddress(HOST, MASTER));
+ domainMasterLifecycleUtil.executeForResult(takeSnapshotOnDomain);
+ domainMasterLifecycleUtil.executeForResult(takeSnapshotOnHost);
+ ModelNode listDomainSnapshots = Util.createEmptyOperation(LIST_SNAPSHOTS_OPERATION, PathAddress.EMPTY_ADDRESS);
+ ModelNode listHostSnapshots = Util.createEmptyOperation(LIST_SNAPSHOTS_OPERATION, PathAddress.pathAddress(HOST, MASTER));
+ ModelNode domainSnapshots = domainMasterLifecycleUtil.executeForResult(listDomainSnapshots);
+ ModelNode hostSnapshots = domainMasterLifecycleUtil.executeForResult(listHostSnapshots);
+
+ defaultDomainConfig = domainSnapshots.get("names").get(0).asString();
+ defaultHostConfig = hostSnapshots.get("names").get(0).asString();
+ }
+ }
+
+ @Override
+ protected void saveDefaultResult() {
+ if (defaultDomainConfigAsFeatures == null || defaultHostConfigAsFeatures == null) {
+ defaultDomainConfigAsFeatures = getConfigAsFeatures(PathAddress.EMPTY_ADDRESS);
+ defaultHostConfigAsFeatures = getConfigAsFeatures(PathAddress.pathAddress(HOST, MASTER));
+ }
+ }
+
+ @Override
+ protected void restoreDefaultConfig() throws TimeoutException, InterruptedException {
+ ModelNode reloadWithSnapshots = Util.createEmptyOperation(RELOAD, PathAddress.pathAddress(HOST, MASTER));
+ reloadWithSnapshots.get(DOMAIN_CONFIG).set(defaultDomainConfig);
+ reloadWithSnapshots.get(HOST_CONFIG).set(defaultHostConfig);
+ domainMasterLifecycleUtil.executeForResult(reloadWithSnapshots);
+ domainMasterLifecycleUtil.awaitHostController(System.currentTimeMillis());
+ }
+
+ private ModelNode getConfigAsFeatures(PathAddress pathAddress) {
+ return domainMasterLifecycleUtil.executeForResult(
+ Util.createEmptyOperation(READ_CONFIG_AS_FEATURES_OPERATION, pathAddress));
+ }
+}
diff --git a/testsuite/shared/src/main/java/org/jboss/as/test/integration/management/api/ReadConfigAsFeaturesTestBase.java b/testsuite/shared/src/main/java/org/jboss/as/test/integration/management/api/ReadConfigAsFeaturesTestBase.java
new file mode 100644
index 00000000000..9042840da64
--- /dev/null
+++ b/testsuite/shared/src/main/java/org/jboss/as/test/integration/management/api/ReadConfigAsFeaturesTestBase.java
@@ -0,0 +1,188 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2018, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.as.test.integration.management.api;
+
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.wildfly.core.testrunner.UnsuccessfulOperationException;
+
+import java.util.concurrent.TimeoutException;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CHILDREN;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ID;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SPEC;
+import static org.jboss.dmr.ModelType.LIST;
+import static org.jboss.dmr.ModelType.OBJECT;
+
+/**
+ * @author Richard Janík
+ */
+public abstract class ReadConfigAsFeaturesTestBase {
+
+ @Before
+ public void setUp() throws UnsuccessfulOperationException {
+ saveDefaultConfig();
+ saveDefaultResult();
+ }
+
+ @After
+ public void tearDown() throws TimeoutException, InterruptedException {
+ restoreDefaultConfig();
+ }
+
+ protected abstract void saveDefaultConfig() throws UnsuccessfulOperationException;
+
+ protected abstract void saveDefaultResult() throws UnsuccessfulOperationException;
+
+ protected abstract void restoreDefaultConfig() throws TimeoutException, InterruptedException;
+
+ protected ModelNode getFeatureNodeChild(ModelNode node, String spec) {
+ return getListElement(node.get(CHILDREN), spec);
+ }
+
+ protected ModelNode getFeatureNodeChild(ModelNode node, String spec, ModelNode id) {
+ return getListElement(node.get(CHILDREN), spec, id);
+ }
+
+ protected int getFeatureNodeChildIndex(ModelNode node, String spec) {
+ return getListElementIndex(node.get(CHILDREN), spec);
+ }
+
+ protected int getFeatureNodeChildIndex(ModelNode node, String spec, ModelNode id) {
+ return getListElementIndex(node.get(CHILDREN), spec, id);
+ }
+
+ protected ModelNode getListElement(ModelNode list, String spec) {
+ return getListElement(list, spec, null);
+ }
+
+ protected ModelNode getListElement(ModelNode list, String spec, ModelNode id) {
+ for (ModelNode element : list.asList()) {
+ if (element.get(SPEC).asString().equals(spec) &&
+ (id == null || id.equals(element.get(ID)))) {
+ return element;
+ }
+ }
+
+ throw new IllegalArgumentException("no element for spec " + spec + " and id " + ((id == null) ? "null" : id.toJSONString(true)));
+ }
+
+ protected int getListElementIndex(ModelNode list, String spec) {
+ return getListElementIndex(list, spec, null);
+ }
+
+ protected int getListElementIndex(ModelNode list, String spec, ModelNode id) {
+ for (int i = 0; i < list.asList().size(); i++) {
+ if (list.get(i).get(SPEC).asString().equals(spec) &&
+ (id == null || id.equals(list.get(i).get(ID)))) {
+ return i;
+ }
+ }
+ throw new IllegalArgumentException("no element for spec " + spec + " and id " + ((id == null) ? "null" : id.toJSONString(true)));
+ }
+
+ private String getProgrammingErrorMessage(String expectedType) {
+ return "model types are expected to be " + expectedType + "LIST, likely a programming error";
+ }
+
+ protected boolean isFeatureNode(ModelNode node) {
+ return ModelType.OBJECT.equals(node.getType()) &&
+ node.hasDefined(SPEC) &&
+ node.hasDefined(ID);
+ }
+
+ /**
+ * This is the entry-point for the {@code ModelNode} comparison, eschewing list order.
+ */
+ protected boolean equalsWithoutListOrder(ModelNode model1, ModelNode model2) {
+ if (!model1.getType().equals(model2.getType())) {
+ return false;
+ }
+
+ switch (model1.getType()) {
+ case OBJECT:
+ return compareObjects(model1, model2);
+ case LIST:
+ return compareLists(model1, model2);
+ default:
+ return model1.equals(model2);
+ }
+ }
+
+ protected boolean compareLists(ModelNode model1, ModelNode model2) {
+ Assert.assertEquals(getProgrammingErrorMessage("LIST"), model1.getType(), model2.getType());
+ Assert.assertEquals(getProgrammingErrorMessage("LIST"), LIST, model1.getType());
+
+ if (!(model1.asList().size() == model2.asList().size())) {
+ return false;
+ }
+
+ for (ModelNode element : model1.asList()) {
+ if (isFeatureNode(element)) {
+ // if it's a feature node, we don't have to do a complicated comparison with every element of the other model
+ try {
+ ModelNode model2Element = getListElement(model2, element.get(SPEC).asString(), element.get(ID));
+ if (!equalsWithoutListOrder(element, model2Element)) return false;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ } else {
+ // if it's not a feature node, let's find a matching element the hard way
+ boolean foundMatch = false;
+ for (ModelNode model2Element : model2.asList()) {
+ foundMatch = equalsWithoutListOrder(element, model2Element);
+ if (foundMatch) break;
+ }
+ if (!foundMatch) return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected boolean compareObjects(ModelNode model1, ModelNode model2) {
+ Assert.assertEquals(getProgrammingErrorMessage("OBJECT"), model1.getType(), model2.getType());
+ Assert.assertEquals(getProgrammingErrorMessage("OBJECT"), OBJECT, model1.getType());
+
+ if (!model1.keys().equals(model2.keys())) {
+ return false;
+ }
+
+ for (String key : model1.keys()) {
+ switch (model1.get(key).getType()) {
+ case OBJECT:
+ if (!compareObjects(model1.get(key), model2.get(key))) return false;
+ break;
+ case LIST:
+ if (!compareLists(model1.get(key), model2.get(key))) return false;
+ break;
+ default:
+ if (!model1.get(key).equals(model2.get(key))) return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/testsuite/standalone/src/test/java/org/wildfly/core/test/standalone/mgmt/api/core/ReadConfigAsFeaturesStandaloneTestCase.java b/testsuite/standalone/src/test/java/org/wildfly/core/test/standalone/mgmt/api/core/ReadConfigAsFeaturesStandaloneTestCase.java
new file mode 100644
index 00000000000..a224e8afd88
--- /dev/null
+++ b/testsuite/standalone/src/test/java/org/wildfly/core/test/standalone/mgmt/api/core/ReadConfigAsFeaturesStandaloneTestCase.java
@@ -0,0 +1,294 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2018, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.wildfly.core.test.standalone.mgmt.api.core;
+
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.operations.common.Util;
+import org.jboss.as.test.integration.management.api.ReadConfigAsFeaturesTestBase;
+import org.jboss.dmr.ModelNode;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.wildfly.core.testrunner.ManagementClient;
+import org.wildfly.core.testrunner.ServerController;
+import org.wildfly.core.testrunner.UnsuccessfulOperationException;
+import org.wildfly.core.testrunner.WildflyTestRunner;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.*;
+
+/**
+ * Tests operation {@code read-config-as-features} in standalone mode.
+ *
+ * @author Richard Janík
+ */
+@RunWith(WildflyTestRunner.class)
+public class ReadConfigAsFeaturesStandaloneTestCase extends ReadConfigAsFeaturesTestBase {
+
+ private File defaultConfig;
+ private ModelNode defaultConfigAsFeatures;
+
+ @Inject
+ private ManagementClient managementClient;
+
+ @Inject
+ private static ServerController serverController;
+
+ @Test
+ public void writeParameterTest() throws UnsuccessfulOperationException {
+ ModelNode writeParameterOpertaion = Util.getWriteAttributeOperation(
+ PathAddress.pathAddress(SUBSYSTEM, "request-controller"),
+ "max-requests", 10);
+
+ ModelNode expectedConfigAsFeatures = defaultConfigAsFeatures.clone();
+ ModelNode requestControllerSubsystem = getFeatureNodeChild(expectedConfigAsFeatures.get(0), "subsystem.request-controller");
+ requestControllerSubsystem.get(PARAMS).set(new ModelNode()).get("max-requests").set(10);
+
+ doTest(Collections.singletonList(writeParameterOpertaion), expectedConfigAsFeatures);
+ }
+
+ @Test
+ public void undefineParameterTest() throws UnsuccessfulOperationException {
+ ModelNode undefineParameterOperation = Util.getUndefineAttributeOperation(
+ PathAddress.pathAddress(SUBSYSTEM, "security-manager").append("deployment-permissions", "default"),
+ "maximum-permissions");
+
+ ModelNode expectedConfigAsFeatures = defaultConfigAsFeatures.clone();
+ ModelNode securityManagerSubsystem = getFeatureNodeChild(expectedConfigAsFeatures.get(0), "subsystem.security-manager");
+ securityManagerSubsystem.get(CHILDREN).get(0).remove(PARAMS);
+
+ doTest(Collections.singletonList(undefineParameterOperation), expectedConfigAsFeatures);
+ }
+
+ @Test
+ public void addChildrenTest() throws UnsuccessfulOperationException {
+ ModelNode addChildrenOperation = Util.createAddOperation(
+ PathAddress.pathAddress(SUBSYSTEM, "jmx").append("configuration", "audit-log"));
+ addChildrenOperation.get("enabled").set(true);
+ addChildrenOperation.get("log-boot").set(true);
+ addChildrenOperation.get("log-read-only").set(false);
+
+ ModelNode expectedConfigAsFeatures = defaultConfigAsFeatures.clone();
+ ModelNode jmxSubsystem = getFeatureNodeChild(expectedConfigAsFeatures.get(0), "subsystem.jmx");
+ ModelNode auditLog = new ModelNode();
+ auditLog.get(SPEC).set("subsystem.jmx.configuration.audit-log");
+ auditLog.get(ID).set(new ModelNode()).get("configuration").set("audit-log");
+ ModelNode params = new ModelNode();
+ params.get("log-read-only").set(false);
+ params.get("log-boot").set(true);
+ params.get("enabled").set(true);
+ auditLog.get(PARAMS).set(params);
+ jmxSubsystem.get(CHILDREN).add(auditLog);
+
+ doTest(Collections.singletonList(addChildrenOperation), expectedConfigAsFeatures);
+ }
+
+ @Test
+ public void removeChildrenTest() throws UnsuccessfulOperationException {
+ ModelNode removeChildrenOperation = Util.createRemoveOperation(
+ PathAddress.pathAddress(SUBSYSTEM, "elytron").append(HTTP_AUTHENTICATION_FACTORY, "management-http-authentication"));
+
+ ModelNode expectedConfigAsFeatures = defaultConfigAsFeatures.clone();
+ ModelNode elytronSubsystem = getFeatureNodeChild(expectedConfigAsFeatures.get(0), "subsystem.elytron");
+ int httpAuthenticationFactoryIndex = getFeatureNodeChildIndex(elytronSubsystem, "subsystem.elytron.http-authentication-factory");
+ elytronSubsystem.get(CHILDREN).remove(httpAuthenticationFactoryIndex);
+
+ doTest(Collections.singletonList(removeChildrenOperation), expectedConfigAsFeatures);
+ }
+
+ @Test
+ public void removeSubsystemTest() throws UnsuccessfulOperationException {
+ ModelNode removeSubsystemOperation = Util.createRemoveOperation(PathAddress.pathAddress(SUBSYSTEM, "discovery"));
+
+ ModelNode expectedConfigAsFeatures = defaultConfigAsFeatures.clone();
+ int discoverySubsystemIndex = getFeatureNodeChildIndex(expectedConfigAsFeatures.get(0), "subsystem.discovery");
+ expectedConfigAsFeatures.get(0).get(CHILDREN).remove(discoverySubsystemIndex);
+
+ doTest(Collections.singletonList(removeSubsystemOperation), expectedConfigAsFeatures);
+ }
+
+ @Test
+ public void coreManagementTest() throws UnsuccessfulOperationException {
+ ModelNode removeSecurityRealm = Util.createRemoveOperation(
+ PathAddress.pathAddress(CORE_SERVICE, MANAGEMENT).append(SECURITY_REALM, "ApplicationRealm"));
+ ModelNode addCustomSecurityRealm = Util.createAddOperation(
+ PathAddress.pathAddress(CORE_SERVICE, MANAGEMENT).append(SECURITY_REALM, "CustomRealm"));
+ addCustomSecurityRealm.get("map-groups-to-roles").set(false);
+ ModelNode configureCustomSecurityRealm = Util.createAddOperation(
+ PathAddress.pathAddress(CORE_SERVICE, MANAGEMENT).append(SECURITY_REALM, "CustomRealm").append("authentication", "local"));
+ configureCustomSecurityRealm.get("default-user").set("john");
+ configureCustomSecurityRealm.get("allowed-users").set("john");
+ configureCustomSecurityRealm.get("skip-group-loading").set(true);
+
+ ModelNode expectedConfigAsFeatures = defaultConfigAsFeatures.clone();
+ ModelNode managementCoreService = getFeatureNodeChild(expectedConfigAsFeatures.get(0), "core-service.management");
+
+ // remove ApplicationRealm
+ ModelNode applicationSecurityRealmId = new ModelNode();
+ applicationSecurityRealmId.get(SECURITY_REALM).set("ApplicationRealm");
+ int applicationSecurityRealmIndex = getFeatureNodeChildIndex(managementCoreService, "core-service.management.security-realm", applicationSecurityRealmId);
+ managementCoreService.get(CHILDREN).remove(applicationSecurityRealmIndex);
+
+ // create model nodes for the new CustomRealm
+ ModelNode customRealmId = new ModelNode();
+ customRealmId.get(SECURITY_REALM).set("CustomRealm");
+ ModelNode customRealmParams = new ModelNode();
+ customRealmParams.get("map-groups-to-roles").set(false);
+
+ // create the authentication model node for the new CustomRealm
+ ModelNode customRealmAuthentication = new ModelNode();
+ customRealmAuthentication.get(SPEC).set("core-service.management.security-realm.authentication.local");
+ ModelNode authenticationId = new ModelNode();
+ authenticationId.get(AUTHENTICATION).set(LOCAL);
+ customRealmAuthentication.get(ID).set(authenticationId);
+ ModelNode authenticationParams = new ModelNode();
+ authenticationParams.get("default-user").set("john");
+ authenticationParams.get("allowed-users").set("john");
+ authenticationParams.get("skip-group-loading").set(true);
+ customRealmAuthentication.get(PARAMS).set(authenticationParams);
+
+ // set up the CustomRealm model node
+ ModelNode customRealm = new ModelNode();
+ customRealm.get(SPEC).set("core-service.management.security-realm");
+ customRealm.get(ID).set(customRealmId);
+ customRealm.get(PARAMS).set(customRealmParams);
+ customRealm.get(CHILDREN).add(customRealmAuthentication);
+
+ // append the CustomRealm model node to the expected model
+ managementCoreService.get(CHILDREN).add(customRealm);
+
+ doTest(Arrays.asList(removeSecurityRealm, addCustomSecurityRealm, configureCustomSecurityRealm), expectedConfigAsFeatures);
+ }
+
+ @Test
+ public void interfaceTest() throws UnsuccessfulOperationException {
+ ModelNode modifyManagementInterface = Util.getWriteAttributeOperation(PathAddress.pathAddress(INTERFACE, "management"), INET_ADDRESS, "10.10.10.10");
+ ModelNode createCustomInterface = Util.createAddOperation(PathAddress.pathAddress(INTERFACE, "custom"));
+ createCustomInterface.get(ANY_ADDRESS).set(true);
+
+ ModelNode expectedConfigAsFeatures = defaultConfigAsFeatures.clone();
+
+ // modify managemnet interface
+ ModelNode managementInterfaceId = new ModelNode();
+ managementInterfaceId.get(INTERFACE).set("management");
+ ModelNode managementInterface = getFeatureNodeChild(expectedConfigAsFeatures.get(0), INTERFACE, managementInterfaceId);
+ managementInterface.get(PARAMS).get(INET_ADDRESS).set("10.10.10.10");
+
+ // add the custom interface
+ ModelNode customInterfaceParams = new ModelNode();
+ ModelNode customInterfaceId = new ModelNode();
+ customInterfaceParams.get(ANY_ADDRESS).set(true);
+ customInterfaceId.get(INTERFACE).set("custom");
+ ModelNode customInterface = new ModelNode();
+ customInterface.get(SPEC).set(INTERFACE);
+ customInterface.get(ID).set(customInterfaceId);
+ customInterface.get(PARAMS).set(customInterfaceParams);
+ expectedConfigAsFeatures.get(0).get(CHILDREN).add(customInterface);
+
+ doTest(Arrays.asList(modifyManagementInterface, createCustomInterface), expectedConfigAsFeatures);
+
+ ModelNode removeCustomInterface = Util.createRemoveOperation(PathAddress.pathAddress(INTERFACE, "custom"));
+ int customInterfaceIndex = getFeatureNodeChildIndex(expectedConfigAsFeatures.get(0), customInterface.get(SPEC).asString(), customInterfaceId);
+ expectedConfigAsFeatures.get(0).get(CHILDREN).remove(customInterfaceIndex);
+
+ doTest(Collections.singletonList(removeCustomInterface), expectedConfigAsFeatures);
+ }
+
+ @Test
+ public void socketBindingGroupTest() throws UnsuccessfulOperationException {
+ ModelNode modifyPortOffsetOperation = Util.getWriteAttributeOperation(PathAddress.pathAddress(SOCKET_BINDING_GROUP, "standard-sockets"), PORT_OFFSET, 100);
+ ModelNode addCustomSocketBindingOperation = Util.createAddOperation(PathAddress.pathAddress(SOCKET_BINDING_GROUP, "standard-sockets").append(SOCKET_BINDING, "custom"));
+ addCustomSocketBindingOperation.get(INTERFACE).set("public");
+ addCustomSocketBindingOperation.get(MULTICAST_ADDRESS).set("230.0.0.10");
+ ModelNode removeCustomSocketBindingOperation = Util.createRemoveOperation(PathAddress.pathAddress(SOCKET_BINDING_GROUP, "standard-sockets").append(SOCKET_BINDING, "custom"));
+
+ ModelNode expectedConfigAsFeatures = defaultConfigAsFeatures.clone();
+
+ // modify the port offset
+ ModelNode standardSocketsId = new ModelNode();
+ standardSocketsId.get(SOCKET_BINDING_GROUP).set("standard-sockets");
+ ModelNode standardSocketBindingGroup = getFeatureNodeChild(expectedConfigAsFeatures.get(0), SOCKET_BINDING_GROUP, standardSocketsId);
+ standardSocketBindingGroup.get(PARAMS).get(PORT_OFFSET).set(100);
+
+ // add custom socket-binding
+ ModelNode customSocketBinding = new ModelNode();
+ ModelNode customSocketBindingId = new ModelNode();
+ ModelNode customSocketBindingParams = new ModelNode();
+ customSocketBindingId.get(SOCKET_BINDING).set("custom");
+ customSocketBindingParams.get(INTERFACE).set("public");
+ customSocketBindingParams.get(MULTICAST_ADDRESS).set("230.0.0.10");
+ customSocketBinding.get(SPEC).set("socket-binding-group.socket-binding");
+ customSocketBinding.get(ID).set(customSocketBindingId);
+ customSocketBinding.get(PARAMS).set(customSocketBindingParams);
+ standardSocketBindingGroup.get(CHILDREN).add(customSocketBinding);
+
+ doTest(Arrays.asList(modifyPortOffsetOperation, addCustomSocketBindingOperation), expectedConfigAsFeatures);
+
+ // remove the custom socket binding
+ int customSocketBindingIndex = getFeatureNodeChildIndex(standardSocketBindingGroup, customSocketBinding.get(SPEC).asString(), customSocketBindingId);
+ standardSocketBindingGroup.get(CHILDREN).remove(customSocketBindingIndex);
+
+ doTest(Collections.singletonList(removeCustomSocketBindingOperation), expectedConfigAsFeatures);
+ }
+
+ private void doTest(List operations, ModelNode expectedConfigAsFeatures) throws UnsuccessfulOperationException {
+ for (ModelNode operation : operations) {
+ managementClient.executeForResult(operation);
+ }
+ if (!equalsWithoutListOrder(expectedConfigAsFeatures, getConfigAsFeatures())) {
+ System.out.println("Actual:\n" + getConfigAsFeatures().toJSONString(false) + "\nExpected:\n" + expectedConfigAsFeatures.toJSONString(false));
+ Assert.fail("There are differences between the expected and the actual model, see the test output for details");
+ }
+ }
+
+ @Override
+ protected void saveDefaultConfig() throws UnsuccessfulOperationException {
+ if (defaultConfig == null) {
+ ModelNode result = managementClient.executeForResult(
+ Util.createEmptyOperation(TAKE_SNAPSHOT_OPERATION, PathAddress.EMPTY_ADDRESS));
+ defaultConfig = Paths.get(result.asString()).toFile();
+ }
+ }
+
+ @Override
+ protected void saveDefaultResult() throws UnsuccessfulOperationException {
+ if (defaultConfigAsFeatures == null) {
+ defaultConfigAsFeatures = getConfigAsFeatures();
+ }
+ }
+
+ @Override
+ protected void restoreDefaultConfig() {
+ serverController.reload(defaultConfig.getName());
+ }
+
+ private ModelNode getConfigAsFeatures() throws UnsuccessfulOperationException {
+ return managementClient.executeForResult(
+ Util.createEmptyOperation(READ_CONFIG_AS_FEATURES_OPERATION, PathAddress.EMPTY_ADDRESS));
+ }
+}