package org.peerbox.filerecovery;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.hive2hive.core.H2HJUnitTest;
import org.hive2hive.core.api.interfaces.IH2HNode;
import org.hive2hive.core.exceptions.NoPeerConnectionException;
import org.hive2hive.core.exceptions.NoSessionException;
import org.hive2hive.core.model.IFileVersion;
import org.hive2hive.core.security.UserCredentials;
import org.hive2hive.core.utils.FileTestUtil;
import org.hive2hive.core.utils.NetworkTestUtil;
import org.hive2hive.core.utils.TestFileConfiguration;
import org.hive2hive.core.utils.TestProcessComponentListener;
import org.hive2hive.core.utils.helper.TestFileAgent;
import org.hive2hive.processframework.exceptions.InvalidProcessStateException;
import org.hive2hive.processframework.exceptions.ProcessExecutionException;
import org.hive2hive.processframework.interfaces.IProcessComponent;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.peerbox.BaseJUnitTest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FileVersionSelectorTest extends BaseJUnitTest {
private static Logger logger = LoggerFactory.getLogger(FileVersionSelectorTest.class);
private static final int NETWORK_SIZE = 6;
private static List<IH2HNode> network;
private static IH2HNode client;
private static UserCredentials userCredentials;
private static File root;
private static File file;
private static int FILE_SIZE = 128*1024;
private static int NUM_VERSIONS = 4;
private static List<String> content;
private static final String fileName = "test-file.txt";
@BeforeClass
public static void beforeClass() throws Exception {
initNetwork();
uploadVersions();
// keep at least (num versions + initial version) many versions
assertTrue(NUM_VERSIONS <= new TestFileConfiguration().getMaxNumOfVersions()-1);
}
private static void initNetwork() throws InvalidProcessStateException,
NoPeerConnectionException, ProcessExecutionException {
network = NetworkTestUtil.createH2HNetwork(NETWORK_SIZE);
client = network.get(RandomUtils.nextInt(0, network.size()));
// register a user
userCredentials = H2HJUnitTest.generateRandomCredentials();
root = FileTestUtil.getTempDirectory();
client.getUserManager().createRegisterProcess(userCredentials).execute();
client.getUserManager().createLoginProcess(userCredentials, new TestFileAgent(root)).execute();
}
private static void uploadVersions() throws Exception {
content = new ArrayList<String>();
// add an intial file to the network
file = new File(root, fileName);
String fileContent = RandomStringUtils.randomAscii(FILE_SIZE);
content.add(fileContent);
logger.info("Initial content: {}...", fileContent.substring(0, 10));
FileUtils.write(file, fileContent);
client.getFileManager().createAddProcess(file).execute();
// update and upload
for(int i = 0; i < NUM_VERSIONS; ++i) {
Thread.sleep(2000); // sleep such that each file has different timestamp
fileContent = RandomStringUtils.randomAscii(FILE_SIZE);
content.add(fileContent);
logger.info("File version {} content: {}...", i, fileContent.substring(0, 10));
FileUtils.write(file, fileContent);
client.getFileManager().createUpdateProcess(file).execute();
}
}
@AfterClass
public static void afterClass() {
NetworkTestUtil.shutdownH2HNetwork(network);
}
@Test
public void testRecoverAllVersions() throws Exception {
// recover all versions
for(int i = 0; i < NUM_VERSIONS; ++ i) {
recoverVersion(i);
}
}
private FileVersionSelectorListener recoverVersion(int version) throws Exception {
// recover version
FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), version);
client.getFileManager().createRecoverProcess(file, versionSelectorListener.getFileVersionSelector()).execute();
// assert content equality
String recoveredFileName = versionSelectorListener.getRecoveredFileName();
assertNotNull(recoveredFileName);
assertFalse(recoveredFileName.isEmpty());
Path recoveredFile = Paths.get(root.toString(), recoveredFileName);
assertTrue(Files.exists(recoveredFile));
String expected = content.get(version);
String recovered = new String(Files.readAllBytes(recoveredFile));
logger.info("Version {}:\n\tExpected content: {}... \n\tRecovered content: {}...",
version, expected.substring(0, 10), recovered.substring(0, 10));
assertTrue(expected.equals(recovered));
return versionSelectorListener;
}
@Test
public void testCancel() throws NoSessionException, NoPeerConnectionException, InvalidProcessStateException, ProcessExecutionException {
// count number of files to make sure no file recovered
int numElementsBefore = root.list().length;
// recover version and cancel
FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), -1);
try {
client.getFileManager().createRecoverProcess(file, versionSelectorListener.getFileVersionSelector()).execute();
fail("Expected exception was not thrown.");
} catch(ProcessExecutionException pex) {
// expected exception since no version selected when cancelled.
logger.info("Exception: {}", pex.getMessage());
}
int numElementsAfter = root.list().length;
assertEquals(numElementsBefore, numElementsAfter);
}
@Test
public void testCancelBeforeSelect() throws NoSessionException, NoPeerConnectionException, InvalidProcessStateException, ProcessExecutionException {
// recover version and cancel
FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), 0);
versionSelectorListener.getFileVersionSelector().cancel(); // cancel before onAvailableVersionsReceived
IProcessComponent<Void> p = client.getFileManager().createRecoverProcess(file, versionSelectorListener.getFileVersionSelector());
TestProcessComponentListener plistener = new TestProcessComponentListener();
p.attachListener(plistener);
try {
p.execute();
fail("Expected exception was not thrown.");
} catch (ProcessExecutionException pex) {
// expected exception since no version selected when cancelled.
logger.info("Exception: {}", pex.getMessage());
}
assertTrue(plistener.hasExecutionFailed());
assertFalse(plistener.hasExecutionSucceeded());
}
@Test(expected=IllegalStateException.class)
public void testSelectBeforeOnAvailableVersions() {
FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), 0);
versionSelectorListener.getFileVersionSelector().selectVersion((IFileVersion)null, file.toPath());
}
@Test(expected=IllegalStateException.class)
public void testSelectTwice() throws FileNotFoundException, NoSessionException, NoPeerConnectionException, InterruptedException, InvalidProcessStateException {
FileVersionSelectorListener versionSelectorListener = new FileVersionSelectorListener(file.toPath(), 0);
List<IFileVersion> versions = new ArrayList<IFileVersion>();
for(int i = 0; i < NUM_VERSIONS; ++i) {
versions.add(null);
}
versionSelectorListener.getFileVersionSelector().selectVersion(versions);
versionSelectorListener.getFileVersionSelector().selectVersion((IFileVersion)null, file.toPath());
}
@Test
public void testRecoverMultipleTimes() throws Exception {
// if same version is recovered multiple times, the file name should have a counter
FileVersionSelectorListener listener_1 = recoverVersion(0);
FileVersionSelectorListener listener_2 = recoverVersion(0);
String recoveredName_1 = listener_1.getRecoveredFileName();
String recoveredName_2 = listener_2.getRecoveredFileName();
assertFalse(recoveredName_1.equals(recoveredName_2));
}
private class FileVersionSelectorListener implements IFileVersionSelectorListener {
private Path fileToRecover;
private int versionToRecover;
private FileVersionSelector versionSelector;
public FileVersionSelectorListener(Path fileToRecover, int versionToRecover) {
this.fileToRecover = fileToRecover;
this.versionToRecover = versionToRecover;
this.versionSelector = new FileVersionSelector(this);
}
public String getRecoveredFileName() {
return versionSelector.getRecoveredFileName();
}
public FileVersionSelector getFileVersionSelector() {
return versionSelector;
}
@Override
public void onAvailableVersionsReceived(List<IFileVersion> availableVersions) {
Assert.assertTrue(availableVersions.size() == NUM_VERSIONS);
Assert.assertTrue(versionToRecover < availableVersions.size());
if(versionToRecover != -1) {
versionSelector.selectVersion(availableVersions.get(versionToRecover), fileToRecover);
} else {
versionSelector.cancel();
}
}
}
}