package org.jboss.as.test.patching; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CHILD_TYPE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST; 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.OUTCOME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_ATTRIBUTE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_CHILDREN_NAMES_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESTART; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SHUTDOWN; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS; import static org.jboss.as.patching.IoUtils.mkdir; import static org.jboss.as.test.patching.PatchingTestUtil.AS_VERSION; import static org.jboss.as.test.patching.PatchingTestUtil.PRODUCT; import static org.jboss.as.test.patching.PatchingTestUtil.createPatchXMLFile; import static org.jboss.as.test.patching.PatchingTestUtil.createZippedPatchFile; import static org.jboss.as.test.patching.PatchingTestUtil.randomString; import java.io.File; import java.io.IOException; import java.util.concurrent.TimeUnit; import org.jboss.as.controller.client.ModelControllerClient; import org.jboss.as.controller.client.Operation; import org.jboss.as.controller.client.OperationBuilder; import org.jboss.as.patching.IoUtils; import org.jboss.as.patching.metadata.ContentModification; import org.jboss.as.patching.metadata.Patch; import org.jboss.as.patching.metadata.PatchBuilder; import org.jboss.as.process.protocol.StreamUtils; import org.jboss.as.test.integration.domain.management.util.DomainLifecycleUtil; import org.jboss.as.test.integration.domain.management.util.DomainTestSupport; import org.jboss.as.version.ProductConfig; import org.jboss.dmr.ModelNode; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; /** * @author Emanuel Muckenhuber */ public class PatchRemoteHostUnitTestCase { private static final ModelNode SLAVE_ADDR = new ModelNode(); private static final ModelNode PATCH_ADDR = new ModelNode(); private static DomainTestSupport testSupport; private static DomainLifecycleUtil domainMasterLifecycleUtil; private static DomainLifecycleUtil domainSlaveLifecycleUtil; private static File tempDir; static { // (host=slave) SLAVE_ADDR.add("host", "slave"); SLAVE_ADDR.protect(); // (host=slave),(core-services=patching) PATCH_ADDR.add("host", "slave"); PATCH_ADDR.add("core-service", "patching"); PATCH_ADDR.protect(); } @BeforeClass public static void setupDomain() throws Exception { tempDir = mkdir(new File(System.getProperty("java.io.tmpdir")), randomString()); testSupport = DomainTestSupport.createAndStartDefaultSupport(PatchRemoteHostUnitTestCase.class.getSimpleName()); domainMasterLifecycleUtil = testSupport.getDomainMasterLifecycleUtil(); domainSlaveLifecycleUtil = testSupport.getDomainSlaveLifecycleUtil(); } @AfterClass public static void tearDownDomain() throws Exception { testSupport.stop(); if (IoUtils.recursiveDelete(tempDir)) { tempDir.deleteOnExit(); } testSupport = null; domainMasterLifecycleUtil = null; domainSlaveLifecycleUtil = null; } @Test public void test() throws Exception { final ModelControllerClient client = domainMasterLifecycleUtil.getDomainClient(); final ModelNode patchOp = new ModelNode(); patchOp.get(OP).set("patch"); patchOp.get(OP_ADDR).set(PATCH_ADDR); final String patchID = "simple-domain-patch"; final File patch = createPatch(patchID); final Operation op = OperationBuilder.create(patchOp) .addFileAsAttachment(patch).build(); try { final ModelNode result = client.execute(op); validateResponse(result); } finally { StreamUtils.safeClose(op); } // Restart the slave restartSlave(client); final ModelNode patchesOp = new ModelNode(); patchesOp.get(OP).set(READ_ATTRIBUTE_OPERATION); patchesOp.get(OP_ADDR).set(PATCH_ADDR); patchesOp.get(NAME).set("patches"); // Check the applied patch final ModelNode entry = new ModelNode().set(patchID); Assert.assertTrue(executeForResult(client, patchesOp).asList().contains(entry)); // Rollback final ModelNode rollback = new ModelNode(); rollback.get(OP).set("rollback"); rollback.get(OP_ADDR).set(PATCH_ADDR); rollback.get("patch-id").set(patchID); rollback.get("reset-configuration").set(false); executeForResult(client, rollback); // Restart restartSlave(client); // Check there is no patch applied Assert.assertTrue(executeForResult(client, patchesOp).asList().isEmpty()); } File createPatch(String patchID) throws Exception { final String fileContent = "Hello World!"; File oneOffPatchDir = mkdir(tempDir, patchID); ContentModification miscFileAdded = ContentModificationUtils.addMisc(oneOffPatchDir, patchID, fileContent, "awesomeDirectory", "awesomeFile"); ProductConfig productConfig = new ProductConfig(PRODUCT, AS_VERSION, "main"); Patch oneOffPatch = PatchBuilder.create() .setPatchId(patchID) .setDescription("A one-off patch adding a misc file.") .oneOffPatchIdentity(productConfig.getProductName(), productConfig.getProductVersion()) .getParent() .addContentModification(miscFileAdded) .build(); createPatchXMLFile(oneOffPatchDir, oneOffPatch); return createZippedPatchFile(oneOffPatchDir, patchID); } void restartSlave(final ModelControllerClient client) throws Exception { final ModelNode restart = new ModelNode(); restart.get(OP).set(SHUTDOWN); restart.get(OP_ADDR).set(SLAVE_ADDR); restart.get(RESTART).set(true); // Shutdown executeForResult(client, restart); Thread.sleep(150); // Wait for the process to finish for (; ; ) { try { domainSlaveLifecycleUtil.getProcessExitCode(); break; } catch (Exception e) { Thread.sleep(1000); } } domainSlaveLifecycleUtil.start(); waitForHost(client, "slave"); } private ModelNode executeForResult(final ModelControllerClient client, final ModelNode operation) throws IOException { final ModelNode result = client.execute(operation); return validateResponse(result); } private ModelNode validateResponse(ModelNode response) { return validateResponse(response, true); } private ModelNode validateResponse(ModelNode response, boolean validateResult) { if (!SUCCESS.equals(response.get(OUTCOME).asString())) { System.out.println("Failed response:"); System.out.println(response); Assert.fail(response.get(FAILURE_DESCRIPTION).toString()); } if (validateResult) { Assert.assertTrue("result exists", response.has(RESULT)); } return response.get(RESULT); } private static void waitForHost(final ModelControllerClient client, final String hostName) throws Exception { final ModelNode operation = new ModelNode(); operation.get(OP).set(READ_CHILDREN_NAMES_OPERATION); operation.get(OP_ADDR).setEmptyList(); operation.get(CHILD_TYPE).set(HOST); final ModelNode host = new ModelNode().set(hostName); final long timeout = 30L; final TimeUnit timeUnit = TimeUnit.SECONDS; final long deadline = System.currentTimeMillis() + timeUnit.toMillis(timeout); for (; ; ) { final long remaining = deadline - System.currentTimeMillis(); final ModelNode result = client.execute(operation); if (result.get(RESULT).asList().contains(host)) { return; } if (remaining <= 0) { Assert.fail(hostName + " did not register within 30 seconds"); } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Assert.fail("Interrupted while waiting for registration of host " + hostName); } } } }