mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 18:38:05 +01:00
Make backupDir/mediaDir indirect
This commit is contained in:
committed by
ravi-signal
parent
de37141812
commit
831c9ff5bf
@@ -29,6 +29,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
@@ -53,6 +54,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
@@ -80,6 +82,7 @@ public class BackupManagerTest {
|
||||
private final RemoteStorageManager remoteStorageManager = mock(RemoteStorageManager.class);
|
||||
private final byte[] backupKey = TestRandomUtil.nextBytes(32);
|
||||
private final UUID aci = UUID.randomUUID();
|
||||
private final SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
private BackupManager backupManager;
|
||||
private BackupsDb backupsDb;
|
||||
@@ -109,14 +112,13 @@ public class BackupManagerTest {
|
||||
testClock.pin(now);
|
||||
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), backupTier);
|
||||
final String encodedBackupId = Base64.getUrlEncoder().encodeToString(hashedBackupId(backupUser.backupId()));
|
||||
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser).join();
|
||||
verify(tusCredentialGenerator, times(1))
|
||||
.generateUpload("%s/%s".formatted(encodedBackupId, BackupManager.MESSAGE_BACKUP_NAME));
|
||||
.generateUpload("%s/%s".formatted(backupUser.backupDir(), BackupManager.MESSAGE_BACKUP_NAME));
|
||||
|
||||
final BackupManager.BackupInfo info = backupManager.backupInfo(backupUser).join();
|
||||
assertThat(info.backupSubdir()).isEqualTo(encodedBackupId);
|
||||
assertThat(info.backupSubdir()).isEqualTo(backupUser.backupDir()).isNotBlank();
|
||||
assertThat(info.messageBackupKey()).isEqualTo(BackupManager.MESSAGE_BACKUP_NAME);
|
||||
assertThat(info.mediaUsedSpace()).isEqualTo(Optional.empty());
|
||||
|
||||
@@ -344,9 +346,7 @@ public class BackupManagerTest {
|
||||
@Test
|
||||
public void quotaEnforcementRecalculation() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final String backupMediaPrefix = "%s/%s/".formatted(
|
||||
BackupManager.encodeBackupIdForCdn(backupUser),
|
||||
BackupManager.MEDIA_DIRECTORY_NAME);
|
||||
final String backupMediaPrefix = "%s/%s/".formatted(backupUser.backupDir(), backupUser.mediaDir());
|
||||
|
||||
// on recalculation, say there's actually 10 bytes left
|
||||
when(remoteStorageManager.calculateBytesUsed(eq(backupMediaPrefix)))
|
||||
@@ -383,9 +383,7 @@ public class BackupManagerTest {
|
||||
final long mediaToAddSize,
|
||||
boolean shouldAccept) {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final String backupMediaPrefix = "%s/%s/".formatted(
|
||||
BackupManager.encodeBackupIdForCdn(backupUser),
|
||||
BackupManager.MEDIA_DIRECTORY_NAME);
|
||||
final String backupMediaPrefix = "%s/%s/".formatted(backupUser.backupDir(), backupUser.mediaDir());
|
||||
|
||||
// set the backupsDb to be out of quota at t=0
|
||||
testClock.pin(Instant.ofEpochSecond(0));
|
||||
@@ -410,9 +408,7 @@ public class BackupManagerTest {
|
||||
public void list(final String cursorVal) {
|
||||
final Optional<String> cursor = Optional.of(cursorVal).filter(StringUtils::isNotBlank);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final String backupMediaPrefix = "%s/%s/".formatted(
|
||||
BackupManager.encodeBackupIdForCdn(backupUser),
|
||||
BackupManager.MEDIA_DIRECTORY_NAME);
|
||||
final String backupMediaPrefix = "%s/%s/".formatted(backupUser.backupDir(), backupUser.mediaDir());
|
||||
|
||||
when(remoteStorageManager.cdnNumber()).thenReturn(13);
|
||||
when(remoteStorageManager.list(eq(backupMediaPrefix), eq(cursor), eq(17L)))
|
||||
@@ -437,9 +433,9 @@ public class BackupManagerTest {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final byte[] mediaId = TestRandomUtil.nextBytes(16);
|
||||
final String backupMediaKey = "%s/%s/%s".formatted(
|
||||
BackupManager.encodeBackupIdForCdn(backupUser),
|
||||
BackupManager.MEDIA_DIRECTORY_NAME,
|
||||
BackupManager.encodeForCdn(mediaId));
|
||||
backupUser.backupDir(),
|
||||
backupUser.mediaDir(),
|
||||
BackupManager.encodeMediaIdForCdn(mediaId));
|
||||
|
||||
backupsDb.setMediaUsage(backupUser, new UsageInfo(100, 1000)).join();
|
||||
|
||||
@@ -474,9 +470,9 @@ public class BackupManagerTest {
|
||||
TestRandomUtil.nextBytes(15));
|
||||
descriptors.add(descriptor);
|
||||
final String backupMediaKey = "%s/%s/%s".formatted(
|
||||
BackupManager.encodeBackupIdForCdn(backupUser),
|
||||
BackupManager.MEDIA_DIRECTORY_NAME,
|
||||
BackupManager.encodeForCdn(descriptor.key()));
|
||||
backupUser.backupDir(),
|
||||
backupUser.mediaDir(),
|
||||
BackupManager.encodeMediaIdForCdn(descriptor.key()));
|
||||
|
||||
initialBytes += i;
|
||||
// fail 2 deletions, otherwise return the corresponding object's size as i
|
||||
@@ -501,9 +497,9 @@ public class BackupManagerTest {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final byte[] mediaId = TestRandomUtil.nextBytes(16);
|
||||
final String backupMediaKey = "%s/%s/%s".formatted(
|
||||
BackupManager.encodeBackupIdForCdn(backupUser),
|
||||
BackupManager.MEDIA_DIRECTORY_NAME,
|
||||
BackupManager.encodeForCdn(mediaId));
|
||||
backupUser.backupDir(),
|
||||
backupUser.mediaDir(),
|
||||
BackupManager.encodeMediaIdForCdn(mediaId));
|
||||
|
||||
backupsDb.setMediaUsage(backupUser, new UsageInfo(100, 5)).join();
|
||||
|
||||
@@ -545,7 +541,7 @@ public class BackupManagerTest {
|
||||
.map(ExpiredBackup::hashedBackupId)
|
||||
.map(ByteBuffer::wrap)
|
||||
.allMatch(expectedHashes::contains)).isTrue();
|
||||
assertThat(expired.stream().allMatch(eb -> eb.backupTierToRemove() == BackupTier.MESSAGES)).isTrue();
|
||||
assertThat(expired.stream().allMatch(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.ALL)).isTrue();
|
||||
|
||||
// on next iteration, backup i should be expired
|
||||
expectedHashes.add(ByteBuffer.wrap(hashedBackupId(backupUsers.get(i).backupId())));
|
||||
@@ -572,50 +568,53 @@ public class BackupManagerTest {
|
||||
|
||||
assertThat(getExpired.apply(Instant.ofEpochSecond(6)))
|
||||
.hasSize(1).first()
|
||||
.matches(eb -> eb.backupTierToRemove() == BackupTier.MEDIA, "is media tier");
|
||||
.matches(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.MEDIA, "is media tier");
|
||||
|
||||
assertThat(getExpired.apply(Instant.ofEpochSecond(7)))
|
||||
.hasSize(1).first()
|
||||
.matches(eb -> eb.backupTierToRemove() == BackupTier.MESSAGES, "is messages tier");
|
||||
.matches(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.ALL, "is messages tier");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(mode = EnumSource.Mode.INCLUDE, names = {"MESSAGES", "MEDIA"})
|
||||
public void deleteBackup(BackupTier backupTier) {
|
||||
@EnumSource(mode = EnumSource.Mode.INCLUDE, names = {"MEDIA", "ALL"})
|
||||
public void expireBackup(ExpiredBackup.ExpirationType expirationType) {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser).join();
|
||||
final String mediaPrefix = "%s/%s/"
|
||||
.formatted(BackupManager.encodeBackupIdForCdn(backupUser), BackupManager.MEDIA_DIRECTORY_NAME);
|
||||
when(remoteStorageManager.list(eq(mediaPrefix), eq(Optional.empty()), anyLong()))
|
||||
|
||||
final String expectedPrefixToDelete = switch (expirationType) {
|
||||
case ALL -> backupUser.backupDir();
|
||||
case MEDIA -> backupUser.backupDir() + "/" + backupUser.mediaDir();
|
||||
case GARBAGE_COLLECTION -> throw new IllegalArgumentException();
|
||||
} + "/";
|
||||
|
||||
when(remoteStorageManager.list(eq(expectedPrefixToDelete), eq(Optional.empty()), anyLong()))
|
||||
.thenReturn(CompletableFuture.completedFuture(new RemoteStorageManager.ListResult(List.of(
|
||||
new RemoteStorageManager.ListResult.Entry("abc", 1),
|
||||
new RemoteStorageManager.ListResult.Entry("def", 1),
|
||||
new RemoteStorageManager.ListResult.Entry("ghi", 1)), Optional.empty())));
|
||||
when(remoteStorageManager.delete(anyString())).thenReturn(CompletableFuture.completedFuture(1L));
|
||||
|
||||
backupManager.deleteBackup(backupTier, hashedBackupId(backupUser.backupId())).join();
|
||||
backupManager.expireBackup(expiredBackup(expirationType, backupUser)).join();
|
||||
verify(remoteStorageManager, times(1)).list(anyString(), any(), anyLong());
|
||||
verify(remoteStorageManager, times(1)).delete(mediaPrefix + "abc");
|
||||
verify(remoteStorageManager, times(1)).delete(mediaPrefix + "def");
|
||||
verify(remoteStorageManager, times(1)).delete(mediaPrefix + "ghi");
|
||||
verify(remoteStorageManager, times(backupTier == BackupTier.MESSAGES ? 1 : 0))
|
||||
.delete("%s/%s".formatted(BackupManager.encodeBackupIdForCdn(backupUser), BackupManager.MESSAGE_BACKUP_NAME));
|
||||
verify(remoteStorageManager, times(1)).delete(expectedPrefixToDelete + "abc");
|
||||
verify(remoteStorageManager, times(1)).delete(expectedPrefixToDelete + "def");
|
||||
verify(remoteStorageManager, times(1)).delete(expectedPrefixToDelete + "ghi");
|
||||
verifyNoMoreInteractions(remoteStorageManager);
|
||||
|
||||
final BackupsDb.TimestampedUsageInfo usage = backupsDb.getMediaUsage(backupUser).join();
|
||||
assertThat(usage.usageInfo().bytesUsed()).isEqualTo(0L);
|
||||
assertThat(usage.usageInfo().numObjects()).isEqualTo(0L);
|
||||
|
||||
if (backupTier == BackupTier.MEDIA) {
|
||||
// should have deleted all the media, but left the backup descriptor in place
|
||||
assertThatNoException().isThrownBy(() -> backupsDb.describeBackup(backupUser).join());
|
||||
} else {
|
||||
if (expirationType == ExpiredBackup.ExpirationType.ALL) {
|
||||
// should have deleted the db row for the backup
|
||||
assertThat(CompletableFutureTestUtil.assertFailsWithCause(
|
||||
StatusRuntimeException.class,
|
||||
backupsDb.describeBackup(backupUser))
|
||||
.getStatus().getCode())
|
||||
.isEqualTo(Status.NOT_FOUND.getCode());
|
||||
} else {
|
||||
// should have deleted all the media, but left the backup descriptor in place
|
||||
assertThatNoException().isThrownBy(() -> backupsDb.describeBackup(backupUser).join());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,8 +622,9 @@ public class BackupManagerTest {
|
||||
public void deleteBackupPaginated() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser).join();
|
||||
final String mediaPrefix = "%s/%s/".formatted(BackupManager.encodeBackupIdForCdn(backupUser),
|
||||
BackupManager.MEDIA_DIRECTORY_NAME);
|
||||
|
||||
final ExpiredBackup expiredBackup = expiredBackup(ExpiredBackup.ExpirationType.MEDIA, backupUser);
|
||||
final String mediaPrefix = expiredBackup.prefixToDelete() + "/";
|
||||
|
||||
// Return 1 item per page. Initially the provided cursor is empty and we'll return the cursor string "1".
|
||||
// When we get the cursor "1", we'll return "2", when "2" we'll return empty indicating listing
|
||||
@@ -647,7 +647,7 @@ public class BackupManagerTest {
|
||||
}));
|
||||
});
|
||||
when(remoteStorageManager.delete(anyString())).thenReturn(CompletableFuture.completedFuture(1L));
|
||||
backupManager.deleteBackup(BackupTier.MEDIA, hashedBackupId(backupUser.backupId())).join();
|
||||
backupManager.expireBackup(expiredBackup).join();
|
||||
verify(remoteStorageManager, times(3)).list(anyString(), any(), anyLong());
|
||||
verify(remoteStorageManager, times(1)).delete(mediaPrefix + "abc");
|
||||
verify(remoteStorageManager, times(1)).delete(mediaPrefix + "def");
|
||||
@@ -655,6 +655,19 @@ public class BackupManagerTest {
|
||||
verifyNoMoreInteractions(remoteStorageManager);
|
||||
}
|
||||
|
||||
private static ExpiredBackup expiredBackup(final ExpiredBackup.ExpirationType expirationType,
|
||||
final AuthenticatedBackupUser backupUser) {
|
||||
return new ExpiredBackup(
|
||||
hashedBackupId(backupUser.backupId()),
|
||||
expirationType,
|
||||
Instant.now(),
|
||||
switch (expirationType) {
|
||||
case ALL -> backupUser.backupDir();
|
||||
case MEDIA -> backupUser.backupDir() + "/" + backupUser.mediaDir();
|
||||
case GARBAGE_COLLECTION -> null;
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, AttributeValue> getBackupItem(final AuthenticatedBackupUser backupUser) {
|
||||
return DYNAMO_DB_EXTENSION.getDynamoDbClient().getItem(GetItemRequest.builder()
|
||||
.tableName(DynamoDbExtensionSchema.Tables.BACKUPS.tableName())
|
||||
@@ -688,6 +701,14 @@ public class BackupManagerTest {
|
||||
}
|
||||
|
||||
private AuthenticatedBackupUser backupUser(final byte[] backupId, final BackupTier backupTier) {
|
||||
return new AuthenticatedBackupUser(backupId, backupTier);
|
||||
// Won't actually validate the public key, but need to have a public key to perform BackupsDB operations
|
||||
byte[] privateKey = new byte[32];
|
||||
ByteBuffer.wrap(privateKey).put(backupId);
|
||||
try {
|
||||
backupsDb.setPublicKey(backupId, backupTier, Curve.decodePrivatePoint(privateKey).publicKey()).join();
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return new AuthenticatedBackupUser(backupId, backupTier, BackupsDb.generateDirName(secureRandom), BackupsDb.generateDirName(secureRandom));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,21 +6,15 @@
|
||||
package org.whispersystems.textsecuregcm.backup;
|
||||
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema;
|
||||
@@ -28,6 +22,12 @@ import org.whispersystems.textsecuregcm.util.CompletableFutureTestUtil;
|
||||
import org.whispersystems.textsecuregcm.util.TestClock;
|
||||
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class BackupsDbTest {
|
||||
|
||||
@@ -75,7 +75,7 @@ public class BackupsDbTest {
|
||||
if (mediaAlreadyExists) {
|
||||
this.backupsDb.trackMedia(backupUser, 1, 10).join();
|
||||
}
|
||||
backupsDb.setMediaUsage(backupUser, new UsageInfo( 113, 17)).join();
|
||||
backupsDb.setMediaUsage(backupUser, new UsageInfo(113, 17)).join();
|
||||
final BackupsDb.TimestampedUsageInfo info = backupsDb.getMediaUsage(backupUser).join();
|
||||
assertThat(info.lastRecalculationTime()).isEqualTo(Instant.ofEpochSecond(5));
|
||||
assertThat(info.usageInfo().bytesUsed()).isEqualTo(113L);
|
||||
@@ -87,6 +87,7 @@ public class BackupsDbTest {
|
||||
final byte[] backupId = TestRandomUtil.nextBytes(16);
|
||||
// Refresh media/messages at t=0
|
||||
testClock.pin(Instant.ofEpochSecond(0L));
|
||||
backupsDb.setPublicKey(backupId, BackupTier.MEDIA, Curve.generateKeyPair().getPublicKey()).join();
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupTier.MEDIA)).join();
|
||||
|
||||
// refresh only messages at t=2
|
||||
@@ -100,10 +101,11 @@ public class BackupsDbTest {
|
||||
|
||||
List<ExpiredBackup> expired = expiredBackups.apply(Instant.ofEpochSecond(1));
|
||||
assertThat(expired).hasSize(1).first()
|
||||
.matches(eb -> eb.backupTierToRemove() == BackupTier.MEDIA);
|
||||
.matches(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.MEDIA);
|
||||
|
||||
// Expire the media
|
||||
backupsDb.clearMediaUsage(expired.get(0).hashedBackupId()).join();
|
||||
backupsDb.startExpiration(expired.get(0)).join();
|
||||
backupsDb.finishExpiration(expired.get(0)).join();
|
||||
|
||||
// should be nothing to expire at t=1
|
||||
assertThat(expiredBackups.apply(Instant.ofEpochSecond(1))).isEmpty();
|
||||
@@ -111,16 +113,100 @@ public class BackupsDbTest {
|
||||
// at t=3, should now expire messages as well
|
||||
expired = expiredBackups.apply(Instant.ofEpochSecond(3));
|
||||
assertThat(expired).hasSize(1).first()
|
||||
.matches(eb -> eb.backupTierToRemove() == BackupTier.MESSAGES);
|
||||
.matches(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.ALL);
|
||||
|
||||
// Expire the messages
|
||||
backupsDb.deleteBackup(expired.get(0).hashedBackupId()).join();
|
||||
backupsDb.startExpiration(expired.get(0)).join();
|
||||
backupsDb.finishExpiration(expired.get(0)).join();
|
||||
|
||||
// should be nothing to expire at t=3
|
||||
assertThat(expiredBackups.apply(Instant.ofEpochSecond(3))).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(names = {"MEDIA", "ALL"})
|
||||
public void expirationFailed(ExpiredBackup.ExpirationType expirationType) {
|
||||
final byte[] backupId = TestRandomUtil.nextBytes(16);
|
||||
// Refresh media/messages at t=0
|
||||
testClock.pin(Instant.ofEpochSecond(0L));
|
||||
backupsDb.setPublicKey(backupId, BackupTier.MEDIA, Curve.generateKeyPair().getPublicKey()).join();
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupTier.MEDIA)).join();
|
||||
|
||||
if (expirationType == ExpiredBackup.ExpirationType.MEDIA) {
|
||||
// refresh only messages at t=2 so that we only expire media at t=1
|
||||
testClock.pin(Instant.ofEpochSecond(2L));
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupTier.MESSAGES)).join();
|
||||
}
|
||||
|
||||
final Function<Instant, Optional<ExpiredBackup>> expiredBackups = purgeTime -> {
|
||||
final List<ExpiredBackup> res = backupsDb
|
||||
.getExpiredBackups(1, Schedulers.immediate(), purgeTime)
|
||||
.collectList()
|
||||
.block();
|
||||
assertThat(res).hasSizeLessThanOrEqualTo(1);
|
||||
return res.stream().findFirst();
|
||||
};
|
||||
|
||||
BackupsDb.AuthenticationData info = backupsDb.retrieveAuthenticationData(backupId).join().get();
|
||||
final String originalBackupDir = info.backupDir();
|
||||
final String originalMediaDir = info.mediaDir();
|
||||
|
||||
ExpiredBackup expired = expiredBackups.apply(Instant.ofEpochSecond(1)).get();
|
||||
assertThat(expired).matches(eb -> eb.expirationType() == expirationType);
|
||||
|
||||
// expire but fail (don't call finishExpiration)
|
||||
backupsDb.startExpiration(expired).join();
|
||||
info = backupsDb.retrieveAuthenticationData(backupId).join().get();
|
||||
if (expirationType == ExpiredBackup.ExpirationType.MEDIA) {
|
||||
// Media expiration should swap the media name and keep the backup name, marking the old media name for expiration
|
||||
assertThat(expired.prefixToDelete())
|
||||
.isEqualTo(originalBackupDir + "/" + originalMediaDir)
|
||||
.withFailMessage("Should expire media directory, expired %s", expired.prefixToDelete());
|
||||
assertThat(info.backupDir()).isEqualTo(originalBackupDir).withFailMessage("should keep backupDir");
|
||||
assertThat(info.mediaDir()).isNotEqualTo(originalMediaDir).withFailMessage("should change mediaDir");
|
||||
} else {
|
||||
// Full expiration should swap the media name and the backup name, marking the old backup name for expiration
|
||||
assertThat(expired.prefixToDelete())
|
||||
.isEqualTo(originalBackupDir)
|
||||
.withFailMessage("Should expire whole backupDir, expired %s", expired.prefixToDelete());
|
||||
assertThat(info.backupDir()).isNotEqualTo(originalBackupDir).withFailMessage("should change backupDir");
|
||||
assertThat(info.mediaDir()).isNotEqualTo(originalMediaDir).withFailMessage("should change mediaDir");
|
||||
}
|
||||
final String expiredPrefix = expired.prefixToDelete();
|
||||
|
||||
// We failed, so we should see the same prefix on the next expiration listing
|
||||
expired = expiredBackups.apply(Instant.ofEpochSecond(1)).get();
|
||||
assertThat(expired).matches(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.GARBAGE_COLLECTION,
|
||||
"Expiration should be garbage collection ");
|
||||
assertThat(expired.prefixToDelete()).isEqualTo(expiredPrefix);
|
||||
backupsDb.startExpiration(expired).join();
|
||||
|
||||
// Successfully finish the expiration
|
||||
backupsDb.finishExpiration(expired).join();
|
||||
|
||||
Optional<ExpiredBackup> opt = expiredBackups.apply(Instant.ofEpochSecond(1));
|
||||
if (expirationType == ExpiredBackup.ExpirationType.MEDIA) {
|
||||
// should be nothing to expire at t=1
|
||||
assertThat(opt).isEmpty();
|
||||
// The backup should still exist
|
||||
backupsDb.describeBackup(backupUser(backupId, BackupTier.MEDIA)).join();
|
||||
} else {
|
||||
// Cleaned up the failed attempt, now should tell us to clean the whole backup
|
||||
assertThat(opt.get()).matches(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.ALL,
|
||||
"Expiration should be all ");
|
||||
backupsDb.startExpiration(opt.get()).join();
|
||||
backupsDb.finishExpiration(opt.get()).join();
|
||||
|
||||
// The backup entry should be gone
|
||||
assertThat(CompletableFutureTestUtil.assertFailsWithCause(StatusRuntimeException.class,
|
||||
backupsDb.describeBackup(backupUser(backupId, BackupTier.MEDIA)))
|
||||
.getStatus().getCode())
|
||||
.isEqualTo(Status.Code.NOT_FOUND);
|
||||
assertThat(expiredBackups.apply(Instant.ofEpochSecond(10))).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private AuthenticatedBackupUser backupUser(final byte[] backupId, final BackupTier backupTier) {
|
||||
return new AuthenticatedBackupUser(backupId, backupTier);
|
||||
return new AuthenticatedBackupUser(backupId, backupTier, "myBackupDir", "myMediaDir");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import javax.ws.rs.client.Invocation;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.checkerframework.checker.units.qual.A;
|
||||
import org.glassfish.jersey.server.ServerProperties;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -289,17 +290,16 @@ public class ArchiveControllerTest {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(
|
||||
new AuthenticatedBackupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
when(backupManager.backupInfo(any())).thenReturn(CompletableFuture.completedFuture(new BackupManager.BackupInfo(
|
||||
1, "subdir", "filename", Optional.empty())));
|
||||
1, "myBackupDir", "myMediaDir", "filename", Optional.empty())));
|
||||
final ArchiveController.BackupInfoResponse response = resources.getJerseyTest()
|
||||
.target("v1/archives")
|
||||
.request()
|
||||
.header("X-Signal-ZK-Auth", Base64.getEncoder().encodeToString(presentation.serialize()))
|
||||
.header("X-Signal-ZK-Auth-Signature", "aaa")
|
||||
.get(ArchiveController.BackupInfoResponse.class);
|
||||
assertThat(response.backupDir()).isEqualTo("subdir");
|
||||
assertThat(response.backupDir()).isEqualTo("myBackupDir");
|
||||
assertThat(response.backupName()).isEqualTo("filename");
|
||||
assertThat(response.cdn()).isEqualTo(1);
|
||||
assertThat(response.usedSpace()).isNull();
|
||||
@@ -310,8 +310,7 @@ public class ArchiveControllerTest {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(
|
||||
new AuthenticatedBackupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
when(backupManager.canStoreMedia(any(), anyLong())).thenReturn(CompletableFuture.completedFuture(true));
|
||||
when(backupManager.copyToBackup(any(), anyInt(), any(), anyInt(), any(), any()))
|
||||
.thenAnswer(invocation -> {
|
||||
@@ -361,8 +360,7 @@ public class ArchiveControllerTest {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(
|
||||
new AuthenticatedBackupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
|
||||
final byte[][] mediaIds = IntStream.range(0, 3).mapToObj(i -> TestRandomUtil.nextBytes(15)).toArray(byte[][]::new);
|
||||
when(backupManager.canStoreMedia(any(), anyLong())).thenReturn(CompletableFuture.completedFuture(true));
|
||||
@@ -417,8 +415,7 @@ public class ArchiveControllerTest {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(
|
||||
new AuthenticatedBackupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
|
||||
when(backupManager.canStoreMedia(any(), eq(1L + 2L + 3L)))
|
||||
.thenReturn(CompletableFuture.completedFuture(false));
|
||||
@@ -448,8 +445,7 @@ public class ArchiveControllerTest {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(
|
||||
new AuthenticatedBackupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
|
||||
final byte[] mediaId = TestRandomUtil.nextBytes(15);
|
||||
final Optional<String> expectedCursor = cursorProvided ? Optional.of("myCursor") : Optional.empty();
|
||||
@@ -484,8 +480,7 @@ public class ArchiveControllerTest {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(BackupTier.MEDIA,
|
||||
backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(
|
||||
new AuthenticatedBackupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
|
||||
final ArchiveController.DeleteMedia deleteRequest = new ArchiveController.DeleteMedia(
|
||||
IntStream
|
||||
@@ -503,4 +498,8 @@ public class ArchiveControllerTest {
|
||||
.post(Entity.json(deleteRequest));
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
}
|
||||
|
||||
private static AuthenticatedBackupUser backupUser(byte[] backupId, BackupTier backupTier) {
|
||||
return new AuthenticatedBackupUser(backupId, backupTier, "myBackupDir", "myMediaDir");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user