/* * JBoss, Home of Professional Open Source * Copyright 2014, JBoss Inc., and individual contributors as indicated * by the @authors tag. * * 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.jboss.as.test.manualmode.deployment; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONTENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INPUT_STREAM_INDEX; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME; 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 java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.UUID; import java.util.function.Supplier; import javax.inject.Inject; import org.apache.commons.io.FileUtils; 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.OperationBuilder; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.protocol.StreamUtils; import org.jboss.as.repository.PathUtil; import org.jboss.as.server.deployment.DeploymentUndeployHandler; import org.jboss.as.test.shared.TestSuiteEnvironment; import org.jboss.as.test.shared.TimeoutUtil; import org.jboss.dmr.ModelNode; import org.jboss.shrinkwrap.api.Archive; 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.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.wildfly.core.testrunner.ServerControl; import org.wildfly.core.testrunner.ServerController; import org.wildfly.core.testrunner.WildflyTestRunner; /** * @author Emanuel Muckenhuber */ @RunWith(WildflyTestRunner.class) @ServerControl(manual = true) public class DeploymentScannerUnitTestCase extends AbstractDeploymentUnitTestCase { private static final String JAR_ONE = "deployment-startup-one.jar"; private static final String JAR_TWO = "deployment-startup-two.jar"; private static final PathAddress DEPLOYMENT_ONE = PathAddress.pathAddress(DEPLOYMENT, JAR_ONE); private static final PathAddress DEPLOYMENT_TWO = PathAddress.pathAddress(DEPLOYMENT, JAR_TWO); private static final int TIMEOUT = TimeoutUtil.adjust(30000); private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss,SSS"); @Inject private ServerController container; private ModelControllerClient client; private static Path deployDir; @Before public void before() throws IOException { deployDir = Files.createTempDirectory("deployment-test-" + UUID.randomUUID()); if (Files.exists(deployDir)) { PathUtil.deleteRecursively(deployDir); } Files.createDirectories(deployDir); } @After public void after() throws IOException { FileUtils.deleteDirectory(deployDir.toFile()); } @Test public void testStartup() throws Exception { final Path oneDeployed = deployDir.resolve(JAR_ONE + ".deployed"); final Path twoFailed = deployDir.resolve(JAR_TWO + ".failed"); container.start(); try { client = TestSuiteEnvironment.getModelControllerClient(); //set the logging to debug addDebugDeploymentLogger(); try { final Path deploymentOne = deployDir.resolve(JAR_ONE); final Path deploymentTwo = deployDir.resolve(JAR_TWO); createDeployment(deploymentOne, "org.jboss.modules"); createDeployment(deploymentTwo, "non.existing.dependency"); addDeploymentScanner(0); try { // Wait until deployed ... long timeout = System.currentTimeMillis() + TIMEOUT; while (!(exists(DEPLOYMENT_ONE) && exists(DEPLOYMENT_TWO)) && System.currentTimeMillis() < timeout) { Thread.sleep(100); } Assert.assertTrue(exists(DEPLOYMENT_ONE)); Assert.assertEquals("OK", deploymentState(DEPLOYMENT_ONE)); Assert.assertTrue(exists(DEPLOYMENT_TWO)); Assert.assertEquals("FAILED", deploymentState(DEPLOYMENT_TWO)); Assert.assertTrue(Files.exists(oneDeployed)); Assert.assertTrue(Files.exists(twoFailed)); // Restart ... client.close(); container.stop(); container.start(); client = TestSuiteEnvironment.getModelControllerClient(); // Wait until started ... timeout = System.currentTimeMillis() + TIMEOUT; while (!isRunning() && System.currentTimeMillis() < timeout) { Thread.sleep(10); } Assert.assertTrue(Files.exists(oneDeployed)); Assert.assertTrue(Files.exists(twoFailed)); Assert.assertTrue(exists(DEPLOYMENT_ONE)); Assert.assertEquals("OK", deploymentState(DEPLOYMENT_ONE)); timeout = System.currentTimeMillis() + TIMEOUT; while (exists(DEPLOYMENT_TWO) && System.currentTimeMillis() < timeout) { Thread.sleep(10); } Assert.assertFalse("Deployment two shouldn't exist at " + TIME_FORMATTER.format(LocalDateTime.now()), exists(DEPLOYMENT_TWO)); ModelNode disableScanner = Util.getWriteAttributeOperation(PathAddress.parseCLIStyleAddress("/subsystem=deployment-scanner/scanner=testScanner"), "scan-interval", 300000); ModelNode result = executeOperation(disableScanner); assertEquals("Unexpected outcome of disabling the test deployment scanner: " + disableScanner, ModelDescriptionConstants.SUCCESS, result.get(OUTCOME).asString()); final ModelNode undeployOp = Util.getEmptyOperation(DeploymentUndeployHandler.OPERATION_NAME, DEPLOYMENT_ONE.toModelNode()); result = executeOperation(undeployOp); assertEquals("Unexpected outcome of undeploying deployment one: " + undeployOp, ModelDescriptionConstants.SUCCESS, result.get(OUTCOME).asString()); Assert.assertTrue(exists(DEPLOYMENT_ONE)); Assert.assertEquals("STOPPED", deploymentState(DEPLOYMENT_ONE)); timeout = System.currentTimeMillis() + TIMEOUT; while (Files.exists(oneDeployed) && System.currentTimeMillis() < timeout) { Thread.sleep(10); } Assert.assertFalse(Files.exists(oneDeployed)); } finally { removeDeploymentScanner(); removeDebugDeploymentLogger(); } } finally { StreamUtils.safeClose(client); } } finally { container.stop(); } } /** * https://bugzilla.redhat.com/show_bug.cgi?id=1291710 * * When FS deployment failed during boot, persistent deployments were removed too. */ @Test public void testFailedDeploymentWithPersistentDeployment() throws Exception { container.start(); try { client = TestSuiteEnvironment.getModelControllerClient(); try { // deploy a persistent deployment Path persistentDeploymentPath = deployDir.resolve(JAR_ONE); PathAddress persistentDeploymentAddress = PathAddress.pathAddress(DEPLOYMENT, JAR_ONE); Archive<?> validDeployment = createDeploymentArchive(); deployPersistent(JAR_ONE, validDeployment); Assert.assertTrue(String.format("%s not deployed", persistentDeploymentPath), exists(persistentDeploymentAddress)); // deploy an invalid file-system deployment addDeploymentScanner(0); try { container.stop(); createDeployment(deployDir.resolve(JAR_TWO), "not.existing.dependency"); container.start(); Path failedMarker = deployDir.resolve(JAR_TWO + ".failed"); waitFor(() -> Files.exists(failedMarker)); Assert.assertTrue(String.format("%s should be deployed", JAR_ONE), exists(persistentDeploymentAddress)); Assert.assertFalse(String.format("%s should not be deployed", JAR_TWO), exists(PathAddress.pathAddress(DEPLOYMENT, JAR_TWO))); Assert.assertTrue(String.format("Missing .failed marker for %s", JAR_TWO), Files.exists(failedMarker)); } finally { removeDeploymentScanner(); } } finally { StreamUtils.safeClose(client); } } finally { container.stop(); } } /** * https://bugzilla.redhat.com/show_bug.cgi?id=997583 * * FS deployments that failed during boot were not removed. */ @Test public void testFailedFileSystemDeploymentDuringBoot() throws Exception { container.start(); try { client = TestSuiteEnvironment.getModelControllerClient(); addDeploymentScanner(0); try { container.stop(); createDeployment(deployDir.resolve(JAR_ONE), "not.existing.dependency"); container.start(); waitFor(() -> Files.exists(deployDir.resolve(JAR_ONE + ".failed"))); assertFailedMarkerCreated(JAR_ONE); Assert.assertFalse(exists(PathAddress.pathAddress(DEPLOYMENT, JAR_ONE))); } finally { removeDeploymentScanner(); } } finally { StreamUtils.safeClose(client); container.stop(); } } @Test public void testFailedDeploymentWithCorrectDeploymentDuringBoot() throws Exception { container.start(); try { client = TestSuiteEnvironment.getModelControllerClient(); addDeploymentScanner(0); try { container.stop(); createDeployment(deployDir.resolve(JAR_ONE), "not.existing.dependency"); createDeployment(deployDir.resolve(JAR_TWO), "org.jboss.modules"); container.start(); waitFor(this::isRunning); assertFailedMarkerCreated(JAR_ONE); Assert.assertFalse(exists(PathAddress.pathAddress(DEPLOYMENT, JAR_ONE))); Assert.assertTrue(exists(PathAddress.pathAddress(DEPLOYMENT, JAR_TWO))); } finally { removeDeploymentScanner(); } } finally { StreamUtils.safeClose(client); container.stop(); } } private void addDebugDeploymentLogger() throws Exception { boolean ok = false; try { final ModelNode op = Util.createAddOperation(getScannerLoggerResourcePath()); op.get("category").set("org.jboss.as.server.deployment.scanner"); op.get("level").set("TRACE"); op.get("use-parent-handlers").set(true); ModelNode result = executeOperation(op); assertEquals("Unexpected outcome of setting the test deployment logger to debug: " + op, SUCCESS, result.get(OUTCOME).asString()); ok = true; } finally { if (!ok) { ModelNode removeOp = Util.createRemoveOperation(getScannerLoggerResourcePath()); ModelNode result = executeOperation(removeOp); assertEquals("Unexpected outcome of removing the test deployment logger: " + removeOp, ModelDescriptionConstants.SUCCESS, result.get(OUTCOME).asString()); } } } private void removeDebugDeploymentLogger() throws Exception { ModelNode removeOp = Util.createRemoveOperation(getScannerLoggerResourcePath()); ModelNode result = executeOperation(removeOp); assertEquals("Unexpected outcome of removing the test deployment logger: " + result, SUCCESS, result.get(OUTCOME).asString()); } private PathAddress getScannerLoggerResourcePath() { return PathAddress.pathAddress(PathElement.pathElement(SUBSYSTEM, "logging"), PathElement.pathElement("logger", "org.jboss.as.server.deployment.scanner")); } private Archive<?> createDeploymentArchive() { final JavaArchive archive = ShrinkWrap.create(JavaArchive.class); final String dependencies = "Dependencies: org.jboss.modules"; archive.add(new StringAsset(dependencies), "META-INF/MANIFEST.MF"); return archive; } private void assertFailedMarkerCreated(String deployment) { Assert.assertTrue(String.format("Failed marker for deployment %s was not created.", deployment), Files.exists(deployDir.resolve(deployment + ".failed"))); } private void waitFor(ExceptionWrappingSupplier<Boolean> condition) throws Exception { long timeout = System.currentTimeMillis() + TimeoutUtil.adjust(TIMEOUT); while (!condition.get() && System.currentTimeMillis() < timeout) { Thread.sleep(100); } } /** * Creates managed deployment * * @param name deployment runtime name * @param archive archive to deploy */ private void deployPersistent(String name, Archive archive) throws IOException { PathAddress address = PathAddress.pathAddress(DEPLOYMENT, name); ModelNode operation = Util.createOperation(ADD, address); operation.get(CONTENT).get(0).get(INPUT_STREAM_INDEX).set(0); OperationBuilder ob = new OperationBuilder(operation, true); ob.addInputStream(archive.as(ZipExporter.class).exportAsInputStream()); ModelNode result = client.execute(ob.build()); Assert.assertEquals(SUCCESS, result.get(OUTCOME).asString()); operation = Util.createOperation(DEPLOY, address); result = client.execute(operation); Assert.assertEquals(SUCCESS, result.get(OUTCOME).asString()); } @Override protected ModelNode executeOperation(ModelNode op) throws IOException { return client.execute(op); } @Override protected File getDeployDir() { return deployDir.toFile(); } /** * A Supplier that wraps eventual checked exception into runtime exception. * * @param <T> the result type */ @FunctionalInterface private interface ExceptionWrappingSupplier<T> extends Supplier<T> { @Override default T get() { try { return throwingGet(); } catch (Exception e) { throw new RuntimeException(e); } } T throwingGet() throws Exception; } }