/*
* JBoss, Home of Professional Open Source.
* Copyright 2014, 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.patching.validation;
import static org.jboss.as.patching.HashUtils.hashFile;
import static org.jboss.as.patching.runner.TestUtils.dump;
import static org.jboss.as.patching.runner.TestUtils.randomString;
import static org.jboss.as.patching.runner.TestUtils.touch;
import static org.jboss.as.patching.validation.PatchHistoryValidations.validateRollbackState;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jboss.as.patching.Constants;
import org.jboss.as.patching.IoUtils;
import org.jboss.as.patching.PatchingException;
import org.jboss.as.patching.tests.AbstractPatchingTest;
import org.jboss.as.patching.tests.PatchingTestBuilder;
import org.jboss.as.patching.tests.PatchingTestStepBuilder;
import org.junit.Assert;
import org.junit.Test;
/**
* @author Alexey Loubyansky
*/
public class PatchingHistoryUnitTestCase extends AbstractPatchingTest {
static final String[] FILE_ONE = {"bin", "standalone.sh"};
static final String[] FILE_TWO = {"bin", "standalone.conf"};
static final String[] FILE_EXISTING = {"bin", "test"};
private static final String CP_1_ID = "cp1";
private static final String ONE_OFF_1_ID = "oneOff1";
private static final String ONE_OFF_2_ID = "oneOff2";
@Test
public void testTreeHandleAll() throws Exception {
installOneOffCpOneOff();
final List<String> historyDirs = new ArrayList<String>();
final List<String> moduleDirs = new ArrayList<String>();
final List<String> bundleDirs = new ArrayList<String>();
final PatchHistoryIterator.Builder builder = PatchHistoryIterator.Builder.create(updateInstallationManager().getDefaultIdentity());
builder.addStateHandler(PatchingArtifacts.HISTORY_DIR, new PatchingArtifactStateHandler<PatchingFileArtifact.DirectoryArtifactState>() {
@Override
public void handleValidatedState(PatchingFileArtifact.DirectoryArtifactState state) {
historyDirs.add(state.getFile().getName());
}
});
builder.addStateHandler(PatchingArtifacts.MODULE_OVERLAY, new PatchingArtifactStateHandler<PatchingFileArtifact.DirectoryArtifactState>() {
@Override
public void handleValidatedState(PatchingFileArtifact.DirectoryArtifactState state) {
moduleDirs.add(state.getFile().getName());
}
});
builder.addStateHandler(PatchingArtifacts.BUNDLE_OVERLAY, new PatchingArtifactStateHandler<PatchingFileArtifact.DirectoryArtifactState>() {
@Override
public void handleValidatedState(PatchingFileArtifact.DirectoryArtifactState state) {
bundleDirs.add(state.getFile().getName());
}
});
final PatchHistoryIterator iterator = builder.iterator();
while (iterator.hasNext()) {
iterator.next();
}
assertEquals(Arrays.asList(new String[]{"oneOff2", "cp1", "oneOff1"}), historyDirs);
assertEquals(Arrays.asList(new String[]{"base-oneOff2", "base-cp1", "base-oneOff1"}), moduleDirs);
assertTrue(bundleDirs.isEmpty());
}
@Test
public void testTreeIterator() throws Exception {
installOneOffCpOneOff();
final List<String> historyDirs = new ArrayList<String>();
final List<String> moduleDirs = new ArrayList<String>();
final List<String> bundleDirs = new ArrayList<String>();
final PatchHistoryIterator.Builder builder = PatchHistoryIterator.Builder.create(updateInstallationManager().getDefaultIdentity());
builder.addStateHandler(PatchingArtifacts.HISTORY_DIR, new PatchingArtifactStateHandler<PatchingFileArtifact.DirectoryArtifactState>() {
@Override
public void handleValidatedState(PatchingFileArtifact.DirectoryArtifactState state) {
historyDirs.add(state.getFile().getName());
}
});
builder.addStateHandler(PatchingArtifacts.MODULE_OVERLAY, new PatchingArtifactStateHandler<PatchingFileArtifact.DirectoryArtifactState>() {
@Override
public void handleValidatedState(PatchingFileArtifact.DirectoryArtifactState state) {
moduleDirs.add(state.getFile().getName());
}
});
builder.addStateHandler(PatchingArtifacts.BUNDLE_OVERLAY, new PatchingArtifactStateHandler<PatchingFileArtifact.DirectoryArtifactState>() {
@Override
public void handleValidatedState(PatchingFileArtifact.DirectoryArtifactState state) {
bundleDirs.add(state.getFile().getName());
}
});
final PatchHistoryIterator tree = builder.iterator();
assertTrue(tree.hasNext());
assertTrue(historyDirs.isEmpty());
assertTrue(moduleDirs.isEmpty());
assertTrue(bundleDirs.isEmpty());
assertTrue(tree.hasNext());
tree.next();
assertEquals(1, historyDirs.size());
assertTrue(historyDirs.contains("oneOff2"));
assertTrue(moduleDirs.contains("base-oneOff2"));
assertTrue(bundleDirs.isEmpty());
assertTrue(tree.hasNext());
tree.next();
assertEquals(2, historyDirs.size());
assertTrue(historyDirs.contains("cp1"));
assertEquals(2, moduleDirs.size());
assertTrue(moduleDirs.contains("base-cp1"));
assertTrue(bundleDirs.isEmpty());
assertTrue(tree.hasNext());
tree.next();
assertEquals(3, historyDirs.size());
assertTrue(historyDirs.contains("oneOff1"));
assertTrue(moduleDirs.contains("base-oneOff1"));
assertTrue(bundleDirs.isEmpty());
assertFalse(tree.hasNext());
}
@Test
public void testSimpleValidRollbackOneOff() throws Exception {
installOneOff();
validateRollbackState(ONE_OFF_1_ID, updateInstallationManager().getDefaultIdentity());
}
@Test
public void testSimpleMissingHistory() throws Exception {
final PatchingTestBuilder builder = installOneOff();
// Delete complete history
final File history = getHistory(builder, ONE_OFF_1_ID);
IoUtils.recursiveDelete(history);
cannotRollbackPatch(ONE_OFF_1_ID);
}
@Test
public void testSimpleMissingMiscFiles() throws Exception {
final PatchingTestBuilder builder = installOneOff();
// Delete misc
final File history = getHistory(builder, ONE_OFF_1_ID);
final File misc = new File(history, Constants.MISC);
IoUtils.recursiveDelete(misc);
cannotRollbackPatch(ONE_OFF_1_ID);
}
@Test
public void testSimpleMissingRollbackXml() throws Exception {
final PatchingTestBuilder builder = installOneOff();
// Delete misc
final File history = getHistory(builder, ONE_OFF_1_ID);
final File rollbackXml = new File(history, Constants.ROLLBACK_XML);
rollbackXml.delete();
cannotRollbackPatch(ONE_OFF_1_ID);
}
@Test
public void testMissingHistoryOneOff() throws Exception {
final PatchingTestBuilder builder = installOneOffCpOneOff();
// Can rollback incl. the first one off
validateRollbackState(ONE_OFF_1_ID, updateInstallationManager().getDefaultIdentity());
// Remove one off history
final File oneOffHistory = getHistory(builder, ONE_OFF_1_ID);
IoUtils.recursiveDelete(oneOffHistory);
cannotRollbackPatch(ONE_OFF_1_ID);
// Can rollback CP1
validateRollbackState(CP_1_ID, updateInstallationManager().getDefaultIdentity());
// Remove cp1 history
final File cpHistory = getHistory(builder, CP_1_ID);
IoUtils.recursiveDelete(cpHistory);
cannotRollbackPatch(CP_1_ID);
// Could still rollback 2nd one off
validateRollbackState(ONE_OFF_2_ID, updateInstallationManager().getDefaultIdentity());
}
@Test
public void testMissingOverlay() throws Exception {
final PatchingTestBuilder builder = installOneOffCpOneOff();
// Can rollback incl. the first one off
validateRollbackState(ONE_OFF_1_ID, updateInstallationManager().getDefaultIdentity());
final File overlays = getOverlays(builder, "base", "base-" + ONE_OFF_1_ID);
IoUtils.recursiveDelete(overlays);
cannotRollbackPatch(ONE_OFF_1_ID);
cannotRollbackPatch(CP_1_ID);
}
protected void cannotRollbackPatch(final String patchID) throws Exception {
try {
validateRollbackState(patchID, updateInstallationManager().getDefaultIdentity());
Assert.fail("should not be able to rollback " + patchID);
} catch (PatchingException e) {
// ok
} catch (Exception e) {
throw e;
}
}
protected PatchingTestBuilder installOneOffCpOneOff() throws IOException, PatchingException {
final PatchingTestBuilder builder = createDefaultBuilder();
final byte[] standaloneHash = new byte[20];
final byte[] moduleHash = new byte[20];
// Create a file
final File existing = builder.getFile(FILE_EXISTING);
touch(existing);
dump(existing, randomString());
final byte[] existingHash = hashFile(existing);
final byte[] initialHash = Arrays.copyOf(existingHash, existingHash.length);
final PatchingTestStepBuilder oneOff1 = builder.createStepBuilder();
oneOff1.setPatchId(ONE_OFF_1_ID)
.oneOffPatchIdentity(PRODUCT_VERSION)
.oneOffPatchElement("base-" + ONE_OFF_1_ID, "base", false)
.addModuleWithRandomContent("org.jboss.test", moduleHash)
.getParent()
.addFileWithRandomContent(standaloneHash, FILE_ONE)
.updateFileWithRandomContent(Arrays.copyOf(existingHash, existingHash.length), existingHash, FILE_EXISTING);
// Apply oneOff1
apply(oneOff1);
final PatchingTestStepBuilder cp1 = builder.createStepBuilder();
cp1.setPatchId(CP_1_ID)
.upgradeIdentity(PRODUCT_VERSION, PRODUCT_VERSION)
.upgradeElement("base-" + CP_1_ID, "base", false)
.updateModuleWithRandomContent("org.jboss.test", Arrays.copyOf(moduleHash, moduleHash.length), moduleHash)
.getParent()
.updateFileWithRandomContent(Arrays.copyOf(standaloneHash, standaloneHash.length), standaloneHash, FILE_ONE)
.updateFileWithRandomContent(initialHash, existingHash, FILE_EXISTING);
// Apply CP1
apply(cp1);
final PatchingTestStepBuilder oneOff2 = builder.createStepBuilder();
oneOff2.setPatchId(ONE_OFF_2_ID)
.oneOffPatchIdentity(PRODUCT_VERSION)
.oneOffPatchElement("base-" + ONE_OFF_2_ID, "base", false)
.updateModuleWithRandomContent("org.jboss.test", moduleHash, null)
.getParent()
.updateFileWithRandomContent(standaloneHash, null, FILE_ONE)
.updateFileWithRandomContent(Arrays.copyOf(existingHash, existingHash.length), existingHash, FILE_EXISTING);
// Apply oneOff1
apply(oneOff2);
return builder;
}
protected PatchingTestBuilder installOneOff() throws Exception {
final PatchingTestBuilder builder = createDefaultBuilder();
final byte[] standaloneHash = new byte[20];
final byte[] moduleHash = new byte[20];
// Create a file
final File existing = builder.getFile(FILE_EXISTING);
touch(existing);
dump(existing, randomString());
final byte[] existingHash = hashFile(existing);
final byte[] initialHash = Arrays.copyOf(existingHash, existingHash.length);
final PatchingTestStepBuilder oneOff1 = builder.createStepBuilder();
oneOff1.setPatchId(ONE_OFF_1_ID)
.oneOffPatchIdentity(PRODUCT_VERSION)
.oneOffPatchElement("base-" + ONE_OFF_1_ID, "base", false)
.addModuleWithRandomContent("org.jboss.test", moduleHash)
.getParent()
.addFileWithRandomContent(standaloneHash, FILE_ONE)
.updateFileWithRandomContent(Arrays.copyOf(existingHash, existingHash.length), existingHash, FILE_EXISTING);
// Apply oneOff1
apply(oneOff1);
return builder;
}
static File getHistory(final PatchingTestBuilder builder, final String patchID) {
return builder.getFile(Constants.INSTALLATION, Constants.PATCHES, patchID);
}
static File getOverlays(final PatchingTestBuilder builder, final String layerName, final String patchID) {
return builder.getFile(Constants.MODULES, Constants.SYSTEM, Constants.LAYERS, layerName, Constants.OVERLAYS, patchID);
}
}