package com.github.ruediste1.btrbck; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import javax.inject.Inject; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.MDC; import com.github.ruediste1.btrbck.dom.ApplicationStreamRepository; import com.github.ruediste1.btrbck.dom.BackupStreamRepository; import com.github.ruediste1.btrbck.dom.RemoteRepository; import com.github.ruediste1.btrbck.dom.Snapshot; import com.github.ruediste1.btrbck.dom.Stream; import com.github.ruediste1.btrbck.dom.StreamRepository; import com.github.ruediste1.btrbck.test.TestBase; public class SnapshotTransferServiceTest extends TestBase { @Inject SnapshotTransferService transferService; @Inject StreamRepositoryService repositoryService; @Inject StreamService streamService; @Inject BtrfsService btrfsService; ApplicationStreamRepository repo1; BackupStreamRepository repo2; private interface ThreadOperation { void run(StreamRepository repo, String streamName, InputStream input, OutputStream output); } private class SshServiceTestDouble extends SshService { private StreamRepository remoteRepo; public SshServiceTestDouble(StreamRepository remoteRepo) { this.remoteRepo = remoteRepo; } @Override public SshConnection receiveSnapshots(RemoteRepository repo, String remoteStreamName, final boolean createRemoteIfNecessary) throws IOException { return doInThread(remoteStreamName, new ThreadOperation() { @Override public void run(StreamRepository repo, String streamName, InputStream input, OutputStream output) { transferService.receiveSnapshots(repo, streamName, input, output, createRemoteIfNecessary); } }); } @Override public SshConnection sendSnapshots(RemoteRepository repo, String remoteStreamName) throws IOException { return doInThread(remoteStreamName, new ThreadOperation() { @Override public void run(StreamRepository repo, String streamName, InputStream input, OutputStream output) { transferService.sendSnapshots(repo, streamName, input, output); } }); } private SshConnection doInThread(final String remoteStreamName, final ThreadOperation operation) throws IOException { final PipedInputStream returnedInput = new PipedInputStream(); final PipedInputStream threadInput = new PipedInputStream(); final PipedOutputStream threadOutput = new PipedOutputStream( returnedInput); final PipedOutputStream returnedOutput = new PipedOutputStream( threadInput); final boolean sudoBtrfs = sudoRemoteBtrfs(); ExecutorService exec = Executors.newSingleThreadExecutor(); final Future<?> future = exec.submit(new Runnable() { @Override public void run() { btrfsService.setUseSudo(sudoBtrfs); MDC.put("id", "2"); try { operation.run(remoteRepo, remoteStreamName, threadInput, threadOutput); } catch (Throwable t) { System.err.println("Error in threaded operation"); System.err.println(t.getMessage()); t.printStackTrace(); throw t; } } }); return new SshConnection() { @Override public OutputStream getOutputStream() { return returnedOutput; } @Override public InputStream getInputStream() { return returnedInput; } @Override public void close() throws Exception { // returnedOutput.flush(); // threadInput.close(); try { future.get(); } catch (ExecutionException e) { try { throw e.getCause(); } catch (DisplayException e1) { throw e1; } catch (Throwable e1) { throw new RuntimeException(e1); } } } }; } } @Before public void setup() throws Exception { repo1 = repositoryService.createRepository( ApplicationStreamRepository.class, createTempDirectory()); repo2 = repositoryService.createRepository( BackupStreamRepository.class, createTempDirectory()); transferService.sshService = new SshServiceTestDouble(repo2); transferService.sshService.setSudoRemoteBtrfs(true); } @After public void tearDown() throws IOException { streamService.deleteStreams(repo1); repositoryService.deleteEmptyRepository(repo1); Files.delete(repo1.rootDirectory); streamService.deleteStreams(repo2); repositoryService.deleteEmptyRepository(repo2); Files.delete(repo2.rootDirectory); } @Test public void transferSnapshotPush() throws Exception { Stream stream = streamService.createStream(repo1, "test"); Stream targetStream = streamService.createStream(repo2, "test2"); Path testFile = repo1.getWorkingDirectory(stream).resolve("test.txt"); Files.copy(new ByteArrayInputStream("Hello".getBytes("UTF-8")), testFile); Snapshot snapshot = streamService.takeSnapshot(stream); transferService.push(stream, null, "test2", false); assertThat(targetStream, not(nullValue())); Path snapshotDir = targetStream.getSnapshotsDir().resolve( snapshot.getSnapshotName()); assertThat(Files.exists(snapshotDir), is(true)); assertThat(Files.exists(snapshotDir.resolve("test.txt")), is(true)); assertEquals(stream.id, streamService.getSnapshots(targetStream).get(0).senderStreamId); } @Test public void transferSnapshotPull() throws Exception { transferService.sshService = new SshServiceTestDouble(repo1); transferService.sshService.setSudoRemoteBtrfs(true); Stream stream = streamService.createStream(repo1, "test"); Stream targetStream = streamService.createStream(repo2, "test2"); Path testFile = repo1.getWorkingDirectory(stream).resolve("test.txt"); Files.copy(new ByteArrayInputStream("Hello".getBytes("UTF-8")), testFile); Snapshot snapshot = streamService.takeSnapshot(stream); transferService.pull(repo2, "test2", null, "test", false); assertThat(targetStream, not(nullValue())); Path snapshotDir = targetStream.getSnapshotsDir().resolve( snapshot.getSnapshotName()); assertThat(Files.exists(snapshotDir), is(true)); assertThat(Files.exists(snapshotDir.resolve("test.txt")), is(true)); assertEquals(stream.id, streamService.getSnapshots(targetStream).get(0).senderStreamId); } @Test public void transferSnapshotCreateStream() throws Exception { Stream stream = streamService.createStream(repo1, "test"); transferService.push(stream, null, "test2", true); Stream targetStream = streamService.tryReadStream(repo2, "test2"); assertThat(targetStream, not(nullValue())); } @Test(expected = DisplayException.class) public void transferSnapshotVersionConflict() throws Exception { Stream stream = streamService.createStream(repo1, "test"); // take and transfer first snapshot streamService.takeSnapshot(stream); transferService.push(stream, null, "test2", true); // change UID. This leads to an // incompatible history stream.versionHistory.getLastEntry().streamId = UUID.randomUUID(); // push should fail transferService.push(stream, null, "test2", false); } }