diff --git a/controller/src/main/java/org/jboss/as/controller/ModelControllerImpl.java b/controller/src/main/java/org/jboss/as/controller/ModelControllerImpl.java index 76ea986bd27..cd6d7e7154b 100644 --- a/controller/src/main/java/org/jboss/as/controller/ModelControllerImpl.java +++ b/controller/src/main/java/org/jboss/as/controller/ModelControllerImpl.java @@ -518,7 +518,7 @@ boolean boot(final List bootList, final OperationMessageHandler handl headers, handler, null, managementModel.get(), control, processState, auditLogger, bootingFlag.get(), true, hostServerGroupTracker, null, notificationSupport, true, extraValidationStepHandler, partialModel, securityIdentitySupplier); - if (configExtension != null && configExtension.shouldProcessOperations(runningModeControl.getRunningMode())) { + if (configExtension != null && configExtension.shouldProcessOperations(runningModeControl.getRunningMode()) && (!runningModeControl.isReloaded() || runningModeControl.isApplyConfigurationExtension())) { configExtension.processOperations(managementModel.get().getRootResourceRegistration(), bootOperations.postExtensionOps); } for (ParsedBootOp parsedOp : bootOperations.postExtensionOps) { diff --git a/controller/src/main/java/org/jboss/as/controller/RunningModeControl.java b/controller/src/main/java/org/jboss/as/controller/RunningModeControl.java index 4274c9ff3d0..342de2e5445 100644 --- a/controller/src/main/java/org/jboss/as/controller/RunningModeControl.java +++ b/controller/src/main/java/org/jboss/as/controller/RunningModeControl.java @@ -17,6 +17,7 @@ public class RunningModeControl { private volatile boolean useCurrentConfig; private volatile String newBootFileName; private volatile Boolean suspend; + private volatile boolean applyConfigurationExtension; public RunningModeControl(final RunningMode initialMode) { this.runningMode = initialMode; @@ -58,6 +59,14 @@ public void setSuspend(Boolean suspend) { this.suspend = suspend; } + public boolean isApplyConfigurationExtension() { + return applyConfigurationExtension; + } + + public void setApplyConfigurationExtension(boolean applyConfigurationExtension) { + this.applyConfigurationExtension = applyConfigurationExtension; + } + /** * Get the new boot file name. For a standalone server this will be the location of the server configuration * (i.e. the standalone.xml variety). For a host controller this will be the location of the host configuration diff --git a/controller/src/main/java/org/jboss/as/controller/logging/ControllerLogger.java b/controller/src/main/java/org/jboss/as/controller/logging/ControllerLogger.java index 7a48a72942e..25083336565 100644 --- a/controller/src/main/java/org/jboss/as/controller/logging/ControllerLogger.java +++ b/controller/src/main/java/org/jboss/as/controller/logging/ControllerLogger.java @@ -3682,7 +3682,7 @@ OperationFailedRuntimeException capabilityAlreadyRegisteredInContext(String capa IllegalArgumentException noResourceForUndefiningAttribute(String attribute, String address); @LogMessage(level = WARN) - @Message(id = 490, value = "You have defined a resource for address %s without any attributes, doing nothing") + @Message(id = 490, value = "A YAML resource has been defined for the address %s without any attribute. No actions will be taken.") void noAttributeSetForAddress(String address); @LogMessage(level = WARN) @@ -3744,7 +3744,7 @@ OperationFailedRuntimeException capabilityAlreadyRegisteredInContext(String capa @Message(id = 501, value = "An invalid UUID string '%s' was found at '%s'. A new value will be generated.") void uuidNotValid(String corruptedUuid, String path); - @Message(id = 502, value = "No child resource called %s could be found at address %s'.") + @Message(id = 502, value = "No child resource called '%s' could be found at address '%s'.") IllegalArgumentException noChildResource(String name, String address); @Message(id = 503, value = "Failed to publish configuration, because the remote name %s is not valid.") @@ -3759,4 +3759,27 @@ OperationFailedRuntimeException capabilityAlreadyRegisteredInContext(String capa @LogMessage(level = WARN) @Message(id = 506, value = "Extension %s from module %s is not enabled by the current stability level") void unstableExtension(String extensionName, String moduleName); + + @Message(id = 507, value = "Unsuported deployment yaml file %s with attributes %s") + IllegalArgumentException unsupportedDeployment(String deployment, Set attribues); + + @Message(id = 508, value = "The yaml element '%s' and its sub-elements are ignored.") + String ignoreYamlElement(String element); + + @Message(id = NONE, value = " Thus ignoring element '%s'.") + String ignoreYamlSubElement(String element); + + @Message(id = 509, value = "No attribute called '%s' is defined at address '%s'.") + IllegalArgumentException noAttributeDefined(String name, String address); + + @Message(id = 510, value = "No operation %s can be executed for attribute called '%s' is defined at address '%s'.") + IllegalArgumentException illegalOperationForAttribute(String operationName, String attribute, String address); + + @LogMessage(level = WARN) + @Message(id = 511, value = "No value is defined for attribute '%s' at address '%s'.") + void noAttributeValueDefined(String name, String address); + + @LogMessage(level = WARN) + @Message(id = 512, value = "No resource exists at address '%s'. Ignoring the remove opreation.") + void removingUnexistingResource(String address); } diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/BackupXmlConfigurationPersister.java b/controller/src/main/java/org/jboss/as/controller/persistence/BackupXmlConfigurationPersister.java index 1302ca8e62c..6c428cb20c8 100644 --- a/controller/src/main/java/org/jboss/as/controller/persistence/BackupXmlConfigurationPersister.java +++ b/controller/src/main/java/org/jboss/as/controller/persistence/BackupXmlConfigurationPersister.java @@ -68,6 +68,7 @@ private static boolean isSuppressLoad(ConfigurationFile configurationFile, boole return initialEmpty && !reload; } + @Override public void registerAdditionalRootElement(final QName anotherRoot, final XMLElementReader> parser){ super.registerAdditionalRootElement(anotherRoot, parser); } @@ -93,13 +94,16 @@ public boolean isPersisting() { public PersistenceResource store(final ModelNode model, Set affectedAddresses) throws ConfigurationPersistenceException { if(!successfulBoot.get()) { return new PersistenceResource() { + @Override public void commit() { } + @Override public void rollback() { } }; } + this.stored = true; return new ConfigurationFilePersistenceResource(model, configurationFile, this); } diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/ConfigurationPersister.java b/controller/src/main/java/org/jboss/as/controller/persistence/ConfigurationPersister.java index 32ad21a7724..e05a06dea5f 100644 --- a/controller/src/main/java/org/jboss/as/controller/persistence/ConfigurationPersister.java +++ b/controller/src/main/java/org/jboss/as/controller/persistence/ConfigurationPersister.java @@ -50,6 +50,17 @@ default boolean isPersisting() { return true; } + /** + * Gets whether a call persist to persistent storage has been successfully completed. + *

+ * The default implementation always returns {@code false} + * + * @return {@code true} if a call to {@link #store(ModelNode, Set)} will return an object that actually writes + */ + default boolean hasStored() { + return false; + } + /** * Persist the given configuration model if {@link #isPersisting()} would return {@code true}, otherwise * return a no-op {@link PersistenceResource}. diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/NullConfigurationPersister.java b/controller/src/main/java/org/jboss/as/controller/persistence/NullConfigurationPersister.java index 51d38e7b7c0..c7f11592a92 100644 --- a/controller/src/main/java/org/jboss/as/controller/persistence/NullConfigurationPersister.java +++ b/controller/src/main/java/org/jboss/as/controller/persistence/NullConfigurationPersister.java @@ -40,6 +40,11 @@ public List load() { return Collections.emptyList(); } + @Override + public boolean hasStored() { + return false; + } + private static class NullPersistenceResource implements ConfigurationPersister.PersistenceResource { private static final NullPersistenceResource INSTANCE = new NullPersistenceResource(); diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/XmlConfigurationPersister.java b/controller/src/main/java/org/jboss/as/controller/persistence/XmlConfigurationPersister.java index 4e0f223f318..7ee06195b0b 100644 --- a/controller/src/main/java/org/jboss/as/controller/persistence/XmlConfigurationPersister.java +++ b/controller/src/main/java/org/jboss/as/controller/persistence/XmlConfigurationPersister.java @@ -44,6 +44,7 @@ public class XmlConfigurationPersister extends AbstractConfigurationPersister { private final XMLElementReader> rootParser; private final Map>> additionalParsers; private final boolean suppressLoad; + protected volatile boolean stored = false; /** * Construct a new instance. @@ -84,6 +85,7 @@ public void registerAdditionalRootElement(final QName anotherRoot, final XMLElem /** {@inheritDoc} */ @Override public PersistenceResource store(final ModelNode model, Set affectedAddresses) throws ConfigurationPersistenceException { + stored = true; return new FilePersistenceResource(model, fileName, this); } @@ -157,4 +159,9 @@ protected void successfulBoot(File file) throws ConfigurationPersistenceExceptio } + @Override + public boolean hasStored() { + return isPersisting() && stored; + } + } diff --git a/controller/src/main/java/org/jboss/as/controller/persistence/yaml/YamlConfigurationExtension.java b/controller/src/main/java/org/jboss/as/controller/persistence/yaml/YamlConfigurationExtension.java index bf5f2b8e8f5..d2af46b6ac1 100644 --- a/controller/src/main/java/org/jboss/as/controller/persistence/yaml/YamlConfigurationExtension.java +++ b/controller/src/main/java/org/jboss/as/controller/persistence/yaml/YamlConfigurationExtension.java @@ -6,10 +6,17 @@ import static org.jboss.as.controller.client.impl.AdditionalBootCliScriptInvoker.CLI_SCRIPT_PROPERTY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.BYTES; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EMPTY; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HASH; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INPUT_STREAM_INDEX; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UNDEFINE_ATTRIBUTE_OPERATION; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.URL; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION; import static org.jboss.as.controller.logging.ControllerLogger.MGMT_OP_LOGGER; @@ -27,6 +34,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -67,6 +75,7 @@ public class YamlConfigurationExtension implements ConfigurationExtension { private static final String CONFIGURATION_ROOT_KEY = "wildfly-configuration"; + private static final String YAML_CODEPOINT_LIMIT = "org.wildfly.configuration.extension.yaml.codepoint.limit"; private static final String YAML_CONFIG = "--yaml"; private static final String SHORT_YAML_CONFIG = "-y"; @@ -74,7 +83,9 @@ public class YamlConfigurationExtension implements ConfigurationExtension { private boolean needReload; private Path[] files; private final List> configs = new ArrayList<>(); - private static final String[] EXCLUDED_ELEMENTS = {" deployment", "extension", "deployment-overlay"}; + private final Map deployments = new LinkedHashMap<>(); + private static final String[] EXCLUDED_ELEMENTS = {"deployment", "extension", "deployment-overlay", "path"}; + public static final Set MANAGED_CONTENT_ATTRIBUTES = Set.of(INPUT_STREAM_INDEX, HASH, BYTES, URL, EMPTY); @SuppressWarnings("unchecked") public YamlConfigurationExtension() { @@ -94,8 +105,13 @@ private void load() { for (Path file : files) { if (file != null && Files.exists(file) && Files.isRegularFile(file)) { Map yamlConfig = Collections.emptyMap(); + LoaderOptions loadingConfig = new LoaderOptions(); + //Default to 3MB + loadingConfig.setCodePointLimit( + Integer.parseInt( + WildFlySecurityManager.getPropertyPrivileged(YAML_CODEPOINT_LIMIT, "3145728"))); + Yaml yaml = new Yaml(new OperationConstructor(loadingConfig)); try (InputStream inputStream = Files.newInputStream(file)) { - Yaml yaml = new Yaml(new OperationConstructor(new LoaderOptions())); yamlConfig = yaml.load(inputStream); } catch (IOException ioex) { throw MGMT_OP_LOGGER.failedToParseYamlConfigurationFile(file.toAbsolutePath().toString(), ioex); @@ -103,7 +119,17 @@ private void load() { if (yamlConfig.containsKey(CONFIGURATION_ROOT_KEY)) { Map config = (Map) yamlConfig.get(CONFIGURATION_ROOT_KEY); for (String excluded : EXCLUDED_ELEMENTS) { - config.remove(excluded); + boolean isPresent = config.containsKey(excluded); + Object value = config.remove(excluded); + if (value != null && value instanceof Map && DEPLOYMENT.equals(excluded)) { + deployments.putAll((Map) value); + } else if (isPresent) { + String message = MGMT_OP_LOGGER.ignoreYamlElement(excluded); + if (value != null) { + message = message + MGMT_OP_LOGGER.ignoreYamlSubElement(yaml.dump(value).trim()); + } + MGMT_OP_LOGGER.warn(message); + } } parsedFiles.add(file.toAbsolutePath().toString()); this.configs.add(config); @@ -142,6 +168,9 @@ public void processOperations(ImmutableManagementResourceRegistration rootRegist for (Map config : configs) { processResource(PathAddress.EMPTY_ADDRESS, new HashMap<>(config), rootRegistration, rootRegistration, xmlOperations, postExtensionOps, false); } + for (Map.Entry deployment : deployments.entrySet()) { + processUnmanagedDeployments(rootRegistration, deployment, xmlOperations, postExtensionOps); + } this.configs.clear(); needReload = true; } @@ -174,7 +203,7 @@ private void processResource(PathAddress parentAddress, Map yaml } else { if (value == null && !isExistingResource(xmlOperations, address)) { //empty resource OperationEntry operationEntry = rootRegistration.getOperationEntry(address, ADD); - if(operationEntry != null) { + if (operationEntry != null) { processAttributes(address, rootRegistration, operationEntry, Collections.emptyMap(), postExtensionOps, xmlOperations); } else { throw MGMT_OP_LOGGER.missingOperationForResource("ADD", address.toCLIStyleString()); @@ -184,13 +213,20 @@ private void processResource(PathAddress parentAddress, Map yaml if (isExistingResource(xmlOperations, address)) { yamlOperation.processOperation(rootRegistration, xmlOperations, postExtensionOps, address, name); } else if (yamlOperation instanceof RemoveOperation) { - //ignore + MGMT_OP_LOGGER.removingUnexistingResource(address.toCLIStyleString()); } else { - throw MGMT_OP_LOGGER.noResourceForUndefiningAttribute(name, address.toCLIStyleString()); + if (yamlOperation instanceof UndefineOperation) { + throw MGMT_OP_LOGGER.noResourceForUndefiningAttribute(name, address.toCLIStyleString()); + } + throw MGMT_OP_LOGGER.illegalOperationForAttribute(yamlOperation.getOperationName(), name, address.toCLIStyleString()); } } else { if (!isExistingResource(xmlOperations, address)) { - MGMT_OP_LOGGER.noAttributeSetForAddress(address.toCLIStyleString()); + if (resourceRegistration.getAttributeNames(PathAddress.EMPTY_ADDRESS).contains(name)) { + MGMT_OP_LOGGER.noAttributeValueDefined(name, address.toCLIStyleString()); + } else { + MGMT_OP_LOGGER.noAttributeSetForAddress(address.toCLIStyleString()); + } } } } @@ -203,13 +239,13 @@ private void processResource(PathAddress parentAddress, Map yaml Object value = yaml.get(name); if (value instanceof Map) { Map map = (Map) value; - if (resourceRegistration.getAttributeNames(PathAddress.EMPTY_ADDRESS).contains(name) && - resourceRegistration.getAttributeAccess(PathAddress.EMPTY_ADDRESS, name).getAttributeDefinition().getType() == OBJECT) { + if (resourceRegistration.getAttributeNames(PathAddress.EMPTY_ADDRESS).contains(name) + && resourceRegistration.getAttributeAccess(PathAddress.EMPTY_ADDRESS, name).getAttributeDefinition().getType() == OBJECT) { processAttribute(address, rootRegistration, name, value, postExtensionOps, xmlOperations); - } else if( !address.equals(parentAddress)) { + } else if (!address.equals(parentAddress)) { processResource(address, map, rootRegistration, rootRegistration.getSubModel(address), xmlOperations, postExtensionOps, false); } else { - throw MGMT_OP_LOGGER.noChildResource(name , address.toCLIStyleString()); + throw MGMT_OP_LOGGER.noChildResource(name, address.toCLIStyleString()); } } else if (value instanceof Operation) { Operation yamlOperation = Operation.class.cast(value); @@ -217,8 +253,16 @@ private void processResource(PathAddress parentAddress, Map yaml } else { if (value != null && resourceRegistration.getAttributeNames(PathAddress.EMPTY_ADDRESS).contains(name)) { //we are processing an attribute: - MGMT_OP_LOGGER.debugf("We are processing the attribute %s for address %s", name, address.getParent().toCLIStyleString()); + MGMT_OP_LOGGER.debugf("We are processing the attribute %s for address %s", name, parentAddress.toCLIStyleString()); processAttribute(parentAddress, rootRegistration, name, value, postExtensionOps, xmlOperations); + } else if (value == null) { + if (resourceRegistration.getAttributeNames(PathAddress.EMPTY_ADDRESS).contains(name)) { + MGMT_OP_LOGGER.noAttributeValueDefined(name, address.toCLIStyleString()); + } else { + MGMT_OP_LOGGER.noAttributeSetForAddress(address.toCLIStyleString()); + } + } else { + throw MGMT_OP_LOGGER.noAttributeDefined(name, address.toCLIStyleString()); } } } else { @@ -233,8 +277,8 @@ private void processResource(PathAddress parentAddress, Map yaml } else { if (!postExtensionOps.isEmpty()) { ParsedBootOp op = postExtensionOps.get(postExtensionOps.size() - 1); - if (! address.equals(op.getAddress())) { // else already processed - Map map = value instanceof Map ? new HashMap<>((Map)value) : new HashMap<>(yaml); + if (!address.equals(op.getAddress())) { // else already processed + Map map = value instanceof Map ? new HashMap<>((Map) value) : new HashMap<>(yaml); //need to process attributes for adding processAttributes(address, rootRegistration, operationEntry, map, postExtensionOps, xmlOperations); processResource(address, map, rootRegistration, resourceRegistration, xmlOperations, postExtensionOps, false); @@ -254,7 +298,15 @@ private void processResource(PathAddress parentAddress, Map yaml processResource(address, map, rootRegistration, childResourceRegistration, xmlOperations, postExtensionOps, false); } else { if (value != null) { - MGMT_OP_LOGGER.unexpectedValueForResource(value, address.toCLIStyleString(), name); + if (value instanceof Operation) { + if (value instanceof RemoveOperation) { + MGMT_OP_LOGGER.removingUnexistingResource(address.toCLIStyleString()); + } else { + MGMT_OP_LOGGER.illegalOperationForAttribute(((Operation) value).getOperationName(), name, address.toCLIStyleString()); + } + } else { + MGMT_OP_LOGGER.unexpectedValueForResource(value, address.toCLIStyleString(), name); + } } } } else if (name.equals(address.getLastElement().getValue())) { @@ -266,7 +318,15 @@ private void processResource(PathAddress parentAddress, Map yaml processResource(address, map, rootRegistration, childResourceRegistration, xmlOperations, postExtensionOps, false); } else { if (value != null) { - MGMT_OP_LOGGER.unexpectedValueForResource(value, address.toCLIStyleString(), name); + if (value instanceof Operation) { + if (value instanceof RemoveOperation) { + MGMT_OP_LOGGER.removingUnexistingResource(address.toCLIStyleString()); + } else { + MGMT_OP_LOGGER.illegalOperationForAttribute(((Operation) value).getOperationName(), name, address.toCLIStyleString()); + } + } else { + MGMT_OP_LOGGER.unexpectedValueForResource(value, address.toCLIStyleString(), name); + } } else {// ADD operation without parameters processAttributes(address, rootRegistration, operationEntry, null, postExtensionOps, xmlOperations); } @@ -376,7 +436,11 @@ private void processAttributes(PathAddress address, ImmutableManagementResourceR processListAttribute((ListAttributeDefinition) att, list, value); break; default: - op.get(att.getName()).set(value.toString()); + if (value != null) { + op.get(att.getName()).set(value.toString()); + } else { + op.get(att.getName()); + } break; } } @@ -385,7 +449,7 @@ private void processAttributes(PathAddress address, ImmutableManagementResourceR ParsedBootOp operation = new ParsedBootOp(op, operationEntry.getOperationHandler()); MGMT_OP_LOGGER.debugf("Adding resource with operation %s", op); postExtensionOps.add(operation); - if(ADD.equals(operationEntry.getOperationDefinition().getName())) { + if (ADD.equals(operationEntry.getOperationDefinition().getName())) { xmlOperations.put(address, operation); } } @@ -405,8 +469,8 @@ private ModelNode processObjectAttribute(ObjectTypeAttributeDefinition att, Map< Object value = map.get(child.getName()); switch (child.getType()) { case OBJECT: - if(child instanceof MapAttributeDefinition) { - processMapAttribute((MapAttributeDefinition)child, objectNode, (Map)value); + if (child instanceof MapAttributeDefinition) { + processMapAttribute((MapAttributeDefinition) child, objectNode, (Map) value); } else { objectNode.get(child.getName()).set(processObjectAttribute((ObjectTypeAttributeDefinition) child, (Map) value)); } @@ -442,11 +506,11 @@ private void processListAttribute(ListAttributeDefinition att, ModelNode list, O @SuppressWarnings("unchecked") private void processMapAttribute(MapAttributeDefinition att, ModelNode map, Map yaml) { - if(att instanceof ObjectMapAttributeDefinition) { + if (att instanceof ObjectMapAttributeDefinition) { ObjectMapAttributeDefinition objectMapAtt = (ObjectMapAttributeDefinition) att; ModelNode objectMapNode = map.get(att.getName()).setEmptyObject(); for (Map.Entry entry : yaml.entrySet()) { - ModelNode objectValue= processObjectAttribute(objectMapAtt.getValueType(), ( Map) entry.getValue()); + ModelNode objectValue = processObjectAttribute(objectMapAtt.getValueType(), (Map) entry.getValue()); objectMapNode.get(entry.getKey()).set(objectValue); } } else { @@ -471,7 +535,26 @@ public String getCommandLineInstructions() { return MGMT_OP_LOGGER.argYaml(); } + @SuppressWarnings("unchecked") + private void processUnmanagedDeployments(ImmutableManagementResourceRegistration rootRegistration, Map.Entry deployment, Map xmlOperations, List postExtensionOps) { + String name = deployment.getKey(); + OperationEntry operationEntry = rootRegistration.getOperationEntry(PathAddress.pathAddress("deployment", name), ADD); + if (deployment.getValue() != null && deployment.getValue() instanceof Map) { + Map attributes = (Map) deployment.getValue(); + Map content = (Map) (((Iterable) attributes.get("content")).iterator().next()); + Set result = content.keySet().stream().distinct().filter(MANAGED_CONTENT_ATTRIBUTES::contains).collect(Collectors.toSet()); + if (!result.isEmpty()) { + throw MGMT_OP_LOGGER.unsupportedDeployment(name, result); + } + PathAddress address = PathAddress.pathAddress(DEPLOYMENT, name); + processAttributes(address, rootRegistration, operationEntry, attributes, postExtensionOps, xmlOperations); + } + } + private interface Operation { + + String getOperationName(); + void processOperation(ImmutableManagementResourceRegistration rootRegistration, Map xmlOperations, List postExtensionOps, PathAddress address, String name); } @@ -509,6 +592,11 @@ public void processOperation(ImmutableManagementResourceRegistration rootRegistr } } + @Override + public String getOperationName() { + return REMOVE; + } + } private class UndefineOperation implements Operation { @@ -525,9 +613,16 @@ public void processOperation(ImmutableManagementResourceRegistration rootRegistr op.get(OP_ADDR).set(address.toModelNode()); op.get(NAME).set(name); postExtensionOps.add(new ParsedBootOp(op, operationEntry.getOperationHandler())); + } else { + throw MGMT_OP_LOGGER.illegalOperationForAttribute(getOperationName(), name, address.toCLIStyleString()); } } + @Override + public String getOperationName() { + return UNDEFINE_ATTRIBUTE_OPERATION; + } + } private class ListAddOperation implements Operation { @@ -541,9 +636,12 @@ private class ListAddOperation implements Operation { @Override @SuppressWarnings("unchecked") public void processOperation(ImmutableManagementResourceRegistration rootRegistration, Map xmlOperations, List postExtensionOps, PathAddress address, String name) { - OperationEntry operationEntry = rootRegistration.getOperationEntry(address, "list-add"); + OperationEntry operationEntry = rootRegistration.getOperationEntry(address, getOperationName()); if (operationEntry != null) { AttributeAccess access = rootRegistration.getAttributeAccess(address, name); + if (!(access.getAttributeDefinition() instanceof ListAttributeDefinition)) { + throw MGMT_OP_LOGGER.illegalOperationForAttribute(getOperationName(), name, address.toCLIStyleString()); + } ListAttributeDefinition att = (ListAttributeDefinition) access.getAttributeDefinition(); AttributeDefinition type = att.getValueAttributeDefinition(); if (type == null) { @@ -613,6 +711,11 @@ public void processOperation(ImmutableManagementResourceRegistration rootRegistr } } + @Override + public String getOperationName() { + return "list-add"; + } + } private class OperationConstructor extends Constructor { diff --git a/controller/src/test/java/org/jboss/as/controller/persistence/yaml/YamlConfigurationExtensionTest.java b/controller/src/test/java/org/jboss/as/controller/persistence/yaml/YamlConfigurationExtensionTest.java index 29b16186445..c5b1fbf4cd9 100644 --- a/controller/src/test/java/org/jboss/as/controller/persistence/yaml/YamlConfigurationExtensionTest.java +++ b/controller/src/test/java/org/jboss/as/controller/persistence/yaml/YamlConfigurationExtensionTest.java @@ -264,7 +264,7 @@ public void testUnknownResource() throws URISyntaxException { instance.processOperations(rootRegistration, postExtensionOps); fail("Unknown resource should make the yaml extension fail"); } catch (java.lang.IllegalArgumentException ex) { - assertEquals("WFLYCTL0502: No child resource called system-propety could be found at address /'.", ex.getMessage()); + assertEquals("WFLYCTL0502: No child resource called 'system-propety' could be found at address '/'.", ex.getMessage()); } } @@ -299,7 +299,7 @@ public void testUnknownChildResource() throws URISyntaxException { try { instance.processOperations(rootRegistration, postExtensionOps);fail("Unknown resource should make the yaml extension fail"); } catch (java.lang.IllegalArgumentException ex) { - assertEquals("WFLYCTL0502: No child resource called children could be found at address /parent=homer'.", ex.getMessage()); + assertEquals("WFLYCTL0502: No child resource called 'children' could be found at address '/parent=homer'.", ex.getMessage()); } } diff --git a/controller/src/test/resources/org/jboss/as/controller/persistence/yaml/deployment.yml b/controller/src/test/resources/org/jboss/as/controller/persistence/yaml/deployment.yml new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/controller/src/test/resources/org/jboss/as/controller/persistence/yaml/deployment.yml @@ -0,0 +1 @@ + diff --git a/controller/src/test/resources/org/jboss/as/controller/persistence/yaml/failed_deployment.yml b/controller/src/test/resources/org/jboss/as/controller/persistence/yaml/failed_deployment.yml new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/controller/src/test/resources/org/jboss/as/controller/persistence/yaml/failed_deployment.yml @@ -0,0 +1 @@ + diff --git a/model-test/src/main/java/org/jboss/as/model/test/StringConfigurationPersister.java b/model-test/src/main/java/org/jboss/as/model/test/StringConfigurationPersister.java index bda6d3dc17f..ed521a8534c 100644 --- a/model-test/src/main/java/org/jboss/as/model/test/StringConfigurationPersister.java +++ b/model-test/src/main/java/org/jboss/as/model/test/StringConfigurationPersister.java @@ -27,6 +27,7 @@ public class StringConfigurationPersister extends AbstractConfigurationPersister private final List bootOperations; private final boolean persistXml; volatile String marshalled; + private volatile boolean stored = false; public StringConfigurationPersister(List bootOperations, XMLElementWriter rootDeparser, boolean persistXml) { super(rootDeparser); @@ -40,6 +41,7 @@ public PersistenceResource store(ModelNode model, Set affectedAddre if (!persistXml) { return new NullConfigurationPersister().store(model, affectedAddresses); } + stored = true; return new StringPersistenceResource(model, this); } @@ -56,6 +58,11 @@ public String getMarshalled() { return marshalled; } + @Override + public boolean hasStored() { + return isPersisting() && stored; + } + private class StringPersistenceResource implements PersistenceResource { private byte[] bytes; diff --git a/server/src/main/java/org/jboss/as/server/controller/resources/ServerRootResourceDefinition.java b/server/src/main/java/org/jboss/as/server/controller/resources/ServerRootResourceDefinition.java index 8135be4c5f9..1748a356f69 100644 --- a/server/src/main/java/org/jboss/as/server/controller/resources/ServerRootResourceDefinition.java +++ b/server/src/main/java/org/jboss/as/server/controller/resources/ServerRootResourceDefinition.java @@ -414,8 +414,9 @@ public void registerOperations(ManagementResourceRegistration resourceRegistrati } else { - ServerProcessReloadHandler.registerStandardReloadOperation(resourceRegistration, runningModeControl, processState, serverEnvironment); - ServerProcessReloadHandler.registerEnhancedReloadOperation(resourceRegistration, runningModeControl, processState, serverEnvironment); + + ServerProcessReloadHandler.registerStandardReloadOperation(resourceRegistration, runningModeControl, processState, serverEnvironment, extensibleConfigurationPersister); + ServerProcessReloadHandler.registerEnhancedReloadOperation(resourceRegistration, runningModeControl, processState, serverEnvironment, extensibleConfigurationPersister); resourceRegistration.registerOperationHandler(ServerSuspendHandler.DEFINITION, ServerSuspendHandler.INSTANCE); resourceRegistration.registerOperationHandler(ServerResumeHandler.DEFINITION, ServerResumeHandler.INSTANCE); diff --git a/server/src/main/java/org/jboss/as/server/operations/ServerDomainProcessReloadHandler.java b/server/src/main/java/org/jboss/as/server/operations/ServerDomainProcessReloadHandler.java index e9151d7f24f..e5031c5f5d5 100644 --- a/server/src/main/java/org/jboss/as/server/operations/ServerDomainProcessReloadHandler.java +++ b/server/src/main/java/org/jboss/as/server/operations/ServerDomainProcessReloadHandler.java @@ -26,7 +26,7 @@ public class ServerDomainProcessReloadHandler extends ServerProcessReloadHandler public ServerDomainProcessReloadHandler(ServiceName rootService, RunningModeControl runningModeControl, ControlledProcessState processState, final DomainServerCommunicationServices.OperationIDUpdater operationIDUpdater, final ServerEnvironment serverEnvironment) { - super(rootService, runningModeControl, processState, serverEnvironment); + super(rootService, runningModeControl, processState, serverEnvironment, null, null); this.operationIDUpdater = operationIDUpdater; } diff --git a/server/src/main/java/org/jboss/as/server/operations/ServerProcessReloadHandler.java b/server/src/main/java/org/jboss/as/server/operations/ServerProcessReloadHandler.java index a57c2af6d18..5902b28b341 100644 --- a/server/src/main/java/org/jboss/as/server/operations/ServerProcessReloadHandler.java +++ b/server/src/main/java/org/jboss/as/server/operations/ServerProcessReloadHandler.java @@ -25,6 +25,7 @@ import org.jboss.as.controller.operations.common.ProcessReloadHandler; import org.jboss.as.controller.operations.validation.EnumValidator; import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.as.controller.persistence.ExtensibleConfigurationPersister; import org.jboss.as.server.ServerEnvironment; import org.jboss.as.server.Services; import org.jboss.as.server.controller.descriptions.ServerDescriptions; @@ -84,24 +85,23 @@ public class ServerProcessReloadHandler extends ProcessReloadHandler additionalAttributes; private final ServerEnvironment environment; + private ExtensibleConfigurationPersister extensibleConfigurationPersister; - protected ServerProcessReloadHandler(ServiceName rootService, RunningModeControl runningModeControl, ControlledProcessState processState, ServerEnvironment environment) { - this(rootService, runningModeControl, processState, environment, null); - } - - private ServerProcessReloadHandler(ServiceName rootService, RunningModeControl runningModeControl, ControlledProcessState processState, ServerEnvironment environment, Set additionalAttributes) { + public ServerProcessReloadHandler(ServiceName rootService, RunningModeControl runningModeControl, + ControlledProcessState processState, ServerEnvironment environment, Set additionalAttributes, ExtensibleConfigurationPersister extensibleConfigurationPersister) { super(rootService, runningModeControl, processState); this.additionalAttributes = additionalAttributes == null ? Collections.emptySet() : additionalAttributes; this.environment = environment; + this.extensibleConfigurationPersister = extensibleConfigurationPersister; } - public static void registerStandardReloadOperation(ManagementResourceRegistration resourceRegistration, RunningModeControl runningModeControl, ControlledProcessState processState, ServerEnvironment serverEnvironment) { - ServerProcessReloadHandler reloadHandler = new ServerProcessReloadHandler(Services.JBOSS_AS, runningModeControl, processState, serverEnvironment); + public static void registerStandardReloadOperation(ManagementResourceRegistration resourceRegistration, RunningModeControl runningModeControl, ControlledProcessState processState, ServerEnvironment serverEnvironment, ExtensibleConfigurationPersister extensibleConfigurationPersister) { + ServerProcessReloadHandler reloadHandler = new ServerProcessReloadHandler(Services.JBOSS_AS, runningModeControl, processState, serverEnvironment, null, extensibleConfigurationPersister); resourceRegistration.registerOperationHandler(ServerProcessReloadHandler.STANDARD_DEFINITION, reloadHandler, false); } - public static void registerEnhancedReloadOperation(ManagementResourceRegistration resourceRegistration, RunningModeControl runningModeControl, ControlledProcessState processState, ServerEnvironment serverEnvironment) { - ServerProcessReloadHandler reloadHandler = new ServerProcessReloadHandler(Services.JBOSS_AS, runningModeControl, processState, serverEnvironment, getAttributeNames(ENHANCED_ATTRIBUTES)); + public static void registerEnhancedReloadOperation(ManagementResourceRegistration resourceRegistration, RunningModeControl runningModeControl, ControlledProcessState processState, ServerEnvironment serverEnvironment, ExtensibleConfigurationPersister extensibleConfigurationPersister) { + ServerProcessReloadHandler reloadHandler = new ServerProcessReloadHandler(Services.JBOSS_AS, runningModeControl, processState, serverEnvironment, getAttributeNames(ENHANCED_ATTRIBUTES), extensibleConfigurationPersister); resourceRegistration.registerOperationHandler(ServerProcessReloadHandler.ENHANCED_DEFINITION, reloadHandler, false); } @@ -148,6 +148,8 @@ protected ProcessReloadHandler.ReloadContext initializeReloa } final boolean finalSuspend = suspend; final boolean finalAdminOnly = adminOnly; + final boolean applyConfigurationExtension = !(context.isNormalServer() || finalAdminOnly) || + (extensibleConfigurationPersister != null && !extensibleConfigurationPersister.hasStored()); final String serverConfig = unmanaged && operation.hasDefined(SERVER_CONFIG.getName()) ? SERVER_CONFIG.resolveModelAttribute(context, operation).asString() : null; @@ -170,10 +172,10 @@ public void doReload(RunningModeControl runningModeControl) { runningModeControl.setUseCurrentConfig(useCurrentConfig); runningModeControl.setNewBootFileName(serverConfig); runningModeControl.setSuspend(finalSuspend); - if (stability != null) { environment.setStability(stability); } + runningModeControl.setApplyConfigurationExtension(applyConfigurationExtension); } }; } diff --git a/testsuite/manualmode/src/test/java/org/jboss/as/test/manualmode/management/persistence/YamlExtensionTestCase.java b/testsuite/manualmode/src/test/java/org/jboss/as/test/manualmode/management/persistence/YamlExtensionTestCase.java index ddbd139334a..8f0bd7f5152 100644 --- a/testsuite/manualmode/src/test/java/org/jboss/as/test/manualmode/management/persistence/YamlExtensionTestCase.java +++ b/testsuite/manualmode/src/test/java/org/jboss/as/test/manualmode/management/persistence/YamlExtensionTestCase.java @@ -5,14 +5,24 @@ package org.jboss.as.test.manualmode.management.persistence; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ARCHIVE; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONTENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PATH; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELATIVE_TO; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RUNNING_MODE; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RUNTIME_NAME; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import jakarta.inject.Inject; +import java.io.BufferedReader; import java.io.IOException; +import java.io.OutputStream; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; import java.util.Iterator; import java.util.regex.Pattern; import org.jboss.as.controller.PathAddress; @@ -23,6 +33,10 @@ import org.jboss.as.test.integration.management.util.CLIWrapper; import org.jboss.as.test.shared.TimeoutUtil; import org.jboss.dmr.ModelNode; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.After; import org.junit.Assert; import org.junit.Assume; @@ -51,8 +65,11 @@ public class YamlExtensionTestCase { @Inject private ServerController container; + private static final Path basedir = new File(WildFlySecurityManager.getPropertyPrivileged("jboss.home", "toto")).toPath().resolve("standalone"); private static Path markerDirectory; private static Path testYaml; + private static Path testDeploymentYaml; + private static Path testManagedDeploymentYaml; private static Path cliScript; private static String expectedXml; private static String expectedBootCLiXml; @@ -61,7 +78,7 @@ public class YamlExtensionTestCase { @BeforeClass public static void setup() throws Exception { Assume.assumeTrue("Layer testing provides a different XML file than the standard one which results in failures", System.getProperty("ts.layers") == null); - Path configurationDir = new File(WildFlySecurityManager.getPropertyPrivileged("jboss.home", "toto")).toPath().resolve("standalone").resolve("configuration"); + Path configurationDir = basedir.resolve("configuration"); Path referenceConfiguration = configurationDir.resolve("reference-standalone.xml"); Files.copy(configurationDir.resolve("standalone.xml"), referenceConfiguration, REPLACE_EXISTING); try (CLIWrapper cli = new CLIWrapper(false)) { @@ -74,8 +91,8 @@ public static void setup() throws Exception { } Path referenceCliConfiguration = configurationDir.resolve("reference-cli-standalone.xml"); Files.copy(configurationDir.resolve("standalone.xml"), referenceCliConfiguration, REPLACE_EXISTING); - Files.copy(new File(YamlExtensionTestCase.class.getResource("bootable-groups.properties").toURI()).toPath(),configurationDir.resolve("bootable-groups.properties"), REPLACE_EXISTING); - Files.copy(new File(YamlExtensionTestCase.class.getResource("bootable-users.properties").toURI()).toPath(),configurationDir.resolve("bootable-users.properties"), REPLACE_EXISTING); + Files.copy(new File(YamlExtensionTestCase.class.getResource("bootable-groups.properties").toURI()).toPath(), configurationDir.resolve("bootable-groups.properties"), REPLACE_EXISTING); + Files.copy(new File(YamlExtensionTestCase.class.getResource("bootable-users.properties").toURI()).toPath(), configurationDir.resolve("bootable-users.properties"), REPLACE_EXISTING); try (CLIWrapper cli = new CLIWrapper(false)) { cli.sendLine("embed-server --admin-only --server-config=reference-cli-standalone.xml"); cli.sendLine("/system-property=foo:add(value=bar)"); @@ -88,6 +105,9 @@ public static void setup() throws Exception { cli.quit(); } testYaml = new File(YamlExtensionTestCase.class.getResource("test.yml").toURI()).toPath().toAbsolutePath(); + testDeploymentYaml = new File(YamlExtensionTestCase.class.getResource("test-deployment.yml").toURI()).toPath().toAbsolutePath(); + testManagedDeploymentYaml = new File(YamlExtensionTestCase.class.getResource("test-managed-deployment.yml").toURI()).toPath().toAbsolutePath(); + createDeployment(configurationDir.getParent().resolve("test.jar")); cliScript = new File(YamlExtensionTestCase.class.getResource("test.cli").toURI()).toPath().toAbsolutePath(); expectedXml = loadFile(referenceConfiguration).replace("\"", "'"); expectedBootCLiXml = loadFile(referenceCliConfiguration).replace("\"", "'"); @@ -138,6 +158,60 @@ public void testSimpleYaml() throws URISyntaxException, Exception { } } + @Test + public void testDeploymentYaml() throws URISyntaxException, Exception { + try { + container.startYamlExtension(new Path[]{testDeploymentYaml}); + try (ModelControllerClient client = container.getClient().getControllerClient()) { + ModelNode deployment = readDeployment(client, "test.jar"); + Assert.assertEquals("test.jar", deployment.get(NAME).asString()); + Assert.assertEquals("test.jar", deployment.get(RUNTIME_NAME).asString()); + ModelNode contentItemNode = deployment.get(CONTENT).get(0); + Assert.assertEquals("test.jar", contentItemNode.get(PATH).asString()); + Assert.assertEquals("jboss.server.base.dir", contentItemNode.get(RELATIVE_TO).asString()); + Assert.assertEquals(true, contentItemNode.get(ARCHIVE).asBoolean()); + deployment = readDeployment(client, "hello.jar"); + Assert.assertEquals("hello.jar", deployment.get(NAME).asString()); + Assert.assertEquals("hello.jar", deployment.get(RUNTIME_NAME).asString()); + contentItemNode = deployment.get(CONTENT).get(0); + Assert.assertEquals("test.jar", contentItemNode.get(PATH).asString()); + Assert.assertEquals("jboss.server.base.dir", contentItemNode.get(RELATIVE_TO).asString()); + Assert.assertEquals(true, contentItemNode.get(ARCHIVE).asBoolean()); + } + } finally { + container.stop(); + } + } + + /** + * Managed deployments are not supported. We should fail + * + * @throws URISyntaxException + * @throws Exception + */ + @Test + public void testFailedDeploymentYaml() throws URISyntaxException, Exception { + try { + container.startYamlExtension(new Path[]{testManagedDeploymentYaml}); + Assert.assertFalse(container.isStarted()); + } catch (RuntimeException ex) { + Assert.assertFalse(container.isStarted()); + try (final BufferedReader reader = Files.newBufferedReader(basedir.resolve("log").resolve("server.log"), StandardCharsets.UTF_8)) { + Assert.assertTrue(reader.lines().anyMatch(line -> line.contains("WFLYCTL0507: Unsuported deployment yaml file hello.jar with attributes [empty]"))); + } + } finally { + container.stop(); + } + } + + private static void createDeployment(Path deployment) throws IOException { + final JavaArchive archive = ShrinkWrap.create(JavaArchive.class); + archive.add(new StringAsset("Dependencies: =org.jboss.modules"), "META-INF/MANIFEST.MF"); + try (OutputStream out = Files.newOutputStream(deployment, StandardOpenOption.CREATE_NEW)) { + archive.as(ZipExporter.class).exportTo(out); + } + } + @Test public void testSimpleYamlWithReload() throws URISyntaxException, Exception { try { @@ -206,7 +280,10 @@ private void compareXML(String expected, String result) { Assert.fail("Missing line " + expectedLines[i] + " in " + System.lineSeparator() + result); } } + } + private ModelNode readDeployment(ModelControllerClient client, String deploymentName) throws IOException { + return Operations.readResult(client.execute(Operations.createReadResourceOperation(PathAddress.pathAddress("deployment", deploymentName).toModelNode()))); } private String readXmlConfig() throws IOException { @@ -218,4 +295,5 @@ private String readXmlConfig() throws IOException { private static String removeWhiteSpaces(String line) { return Pattern.compile("(^\\s*$\\r?\\n)+", Pattern.MULTILINE).matcher(line.stripTrailing()).replaceAll(""); } + } diff --git a/testsuite/manualmode/src/test/resources/org/jboss/as/test/manualmode/management/persistence/test-deployment.yml b/testsuite/manualmode/src/test/resources/org/jboss/as/test/manualmode/management/persistence/test-deployment.yml new file mode 100644 index 00000000000..c53e42dea63 --- /dev/null +++ b/testsuite/manualmode/src/test/resources/org/jboss/as/test/manualmode/management/persistence/test-deployment.yml @@ -0,0 +1,14 @@ +wildfly-configuration: + deployment: + test.jar: + content: + - + path: test.jar + relative-to: jboss.server.base.dir + archive: true + hello.jar: + content: + - + path: test.jar + relative-to: jboss.server.base.dir + archive: true \ No newline at end of file diff --git a/testsuite/manualmode/src/test/resources/org/jboss/as/test/manualmode/management/persistence/test-managed-deployment.yml b/testsuite/manualmode/src/test/resources/org/jboss/as/test/manualmode/management/persistence/test-managed-deployment.yml new file mode 100644 index 00000000000..61c84f5ee03 --- /dev/null +++ b/testsuite/manualmode/src/test/resources/org/jboss/as/test/manualmode/management/persistence/test-managed-deployment.yml @@ -0,0 +1,12 @@ +wildfly-configuration: + deployment: + test.jar: + content: + - + path: test.jar + relative-to: jboss.server.base.dir + archive: true + hello.jar: + content: + - + empty: true \ No newline at end of file