2016-08-01 18:04:23 +00:00
|
|
|
package repository_test
|
2016-08-01 16:55:07 +00:00
|
|
|
|
|
|
|
import (
|
2017-06-05 21:56:59 +00:00
|
|
|
"context"
|
2016-08-01 16:55:07 +00:00
|
|
|
"math/rand"
|
|
|
|
"testing"
|
2020-11-02 11:55:34 +00:00
|
|
|
"time"
|
2017-07-23 12:21:03 +00:00
|
|
|
|
|
|
|
"github.com/restic/restic/internal/index"
|
|
|
|
"github.com/restic/restic/internal/repository"
|
2017-07-24 15:42:25 +00:00
|
|
|
"github.com/restic/restic/internal/restic"
|
2016-08-01 16:55:07 +00:00
|
|
|
)
|
|
|
|
|
2016-08-01 18:24:15 +00:00
|
|
|
func randomSize(min, max int) int {
|
|
|
|
return rand.Intn(max-min) + min
|
|
|
|
}
|
|
|
|
|
2016-09-03 11:34:04 +00:00
|
|
|
func createRandomBlobs(t testing.TB, repo restic.Repository, blobs int, pData float32) {
|
2016-08-01 18:24:15 +00:00
|
|
|
for i := 0; i < blobs; i++ {
|
|
|
|
var (
|
2016-08-31 18:58:57 +00:00
|
|
|
tpe restic.BlobType
|
2016-08-01 18:24:15 +00:00
|
|
|
length int
|
|
|
|
)
|
|
|
|
|
|
|
|
if rand.Float32() < pData {
|
2016-08-31 21:07:50 +00:00
|
|
|
tpe = restic.DataBlob
|
2016-08-04 18:42:11 +00:00
|
|
|
length = randomSize(10*1024, 1024*1024) // 10KiB to 1MiB of data
|
2016-08-01 18:24:15 +00:00
|
|
|
} else {
|
2016-08-31 21:07:50 +00:00
|
|
|
tpe = restic.TreeBlob
|
2016-08-04 18:42:11 +00:00
|
|
|
length = randomSize(1*1024, 20*1024) // 1KiB to 20KiB
|
2016-08-01 18:24:15 +00:00
|
|
|
}
|
|
|
|
|
2020-03-09 08:54:24 +00:00
|
|
|
buf := make([]byte, length)
|
|
|
|
rand.Read(buf)
|
2016-08-04 18:42:11 +00:00
|
|
|
|
2020-06-06 20:20:44 +00:00
|
|
|
id, exists, err := repo.SaveBlob(context.TODO(), tpe, buf, restic.ID{}, false)
|
2016-08-01 18:24:15 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("SaveFrom() error %v", err)
|
|
|
|
}
|
|
|
|
|
2020-06-06 20:20:44 +00:00
|
|
|
if exists {
|
|
|
|
t.Errorf("duplicate blob %v/%v ignored", id, restic.DataBlob)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-08-01 18:24:15 +00:00
|
|
|
if rand.Float32() < 0.2 {
|
2017-11-22 11:27:29 +00:00
|
|
|
if err = repo.Flush(context.Background()); err != nil {
|
2016-08-01 18:24:15 +00:00
|
|
|
t.Fatalf("repo.Flush() returned error %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-22 11:27:29 +00:00
|
|
|
if err := repo.Flush(context.Background()); err != nil {
|
2016-08-01 18:24:15 +00:00
|
|
|
t.Fatalf("repo.Flush() returned error %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 15:04:48 +00:00
|
|
|
func createRandomWrongBlob(t testing.TB, repo restic.Repository) {
|
|
|
|
length := randomSize(10*1024, 1024*1024) // 10KiB to 1MiB of data
|
|
|
|
buf := make([]byte, length)
|
|
|
|
rand.Read(buf)
|
|
|
|
id := restic.Hash(buf)
|
|
|
|
// invert first data byte
|
|
|
|
buf[0] ^= 0xff
|
|
|
|
|
|
|
|
_, _, err := repo.SaveBlob(context.TODO(), restic.DataBlob, buf, id, false)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("SaveFrom() error %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := repo.Flush(context.Background()); err != nil {
|
|
|
|
t.Fatalf("repo.Flush() returned error %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-01 18:24:15 +00:00
|
|
|
// selectBlobs splits the list of all blobs randomly into two lists. A blob
|
|
|
|
// will be contained in the firstone ith probability p.
|
2016-09-03 11:34:04 +00:00
|
|
|
func selectBlobs(t *testing.T, repo restic.Repository, p float32) (list1, list2 restic.BlobSet) {
|
2016-08-31 21:07:50 +00:00
|
|
|
list1 = restic.NewBlobSet()
|
|
|
|
list2 = restic.NewBlobSet()
|
2016-08-01 18:24:15 +00:00
|
|
|
|
2016-08-31 21:07:50 +00:00
|
|
|
blobs := restic.NewBlobSet()
|
2016-08-04 18:42:11 +00:00
|
|
|
|
2020-08-16 09:16:38 +00:00
|
|
|
err := repo.List(context.TODO(), restic.PackFile, func(id restic.ID, size int64) error {
|
2018-01-24 02:43:21 +00:00
|
|
|
entries, _, err := repo.ListPack(context.TODO(), id, size)
|
2016-08-01 18:24:15 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error listing pack %v: %v", id, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
2016-08-31 21:07:50 +00:00
|
|
|
h := restic.BlobHandle{ID: entry.ID, Type: entry.Type}
|
2016-08-04 18:42:11 +00:00
|
|
|
if blobs.Has(h) {
|
|
|
|
t.Errorf("ignoring duplicate blob %v", h)
|
2018-01-21 16:25:36 +00:00
|
|
|
return nil
|
2016-08-04 18:42:11 +00:00
|
|
|
}
|
|
|
|
blobs.Insert(h)
|
|
|
|
|
2016-08-01 18:24:15 +00:00
|
|
|
if rand.Float32() <= p {
|
2016-08-31 21:07:50 +00:00
|
|
|
list1.Insert(restic.BlobHandle{ID: entry.ID, Type: entry.Type})
|
2016-08-01 18:24:15 +00:00
|
|
|
} else {
|
2016-08-31 21:07:50 +00:00
|
|
|
list2.Insert(restic.BlobHandle{ID: entry.ID, Type: entry.Type})
|
2016-08-01 18:24:15 +00:00
|
|
|
}
|
|
|
|
}
|
2018-01-21 16:25:36 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2016-08-01 18:24:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return list1, list2
|
|
|
|
}
|
|
|
|
|
2016-09-03 11:34:04 +00:00
|
|
|
func listPacks(t *testing.T, repo restic.Repository) restic.IDSet {
|
2016-08-31 18:29:54 +00:00
|
|
|
list := restic.NewIDSet()
|
2020-08-16 09:16:38 +00:00
|
|
|
err := repo.List(context.TODO(), restic.PackFile, func(id restic.ID, size int64) error {
|
2016-08-01 18:24:15 +00:00
|
|
|
list.Insert(id)
|
2018-01-21 16:25:36 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2016-08-01 18:24:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return list
|
|
|
|
}
|
|
|
|
|
2016-09-03 11:34:04 +00:00
|
|
|
func findPacksForBlobs(t *testing.T, repo restic.Repository, blobs restic.BlobSet) restic.IDSet {
|
2016-08-31 18:29:54 +00:00
|
|
|
packs := restic.NewIDSet()
|
2016-08-01 18:24:15 +00:00
|
|
|
|
|
|
|
idx := repo.Index()
|
2016-08-03 20:38:05 +00:00
|
|
|
for h := range blobs {
|
2020-06-14 11:26:10 +00:00
|
|
|
list := idx.Lookup(h.ID, h.Type)
|
|
|
|
if len(list) == 0 {
|
2018-01-12 06:20:12 +00:00
|
|
|
t.Fatal("Failed to find blob", h.ID.Str(), "with type", h.Type)
|
2016-08-01 18:24:15 +00:00
|
|
|
}
|
|
|
|
|
2016-08-03 20:38:05 +00:00
|
|
|
for _, pb := range list {
|
|
|
|
packs.Insert(pb.PackID)
|
|
|
|
}
|
2016-08-01 18:24:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return packs
|
|
|
|
}
|
|
|
|
|
2016-09-03 11:34:04 +00:00
|
|
|
func repack(t *testing.T, repo restic.Repository, packs restic.IDSet, blobs restic.BlobSet) {
|
2017-06-15 12:40:34 +00:00
|
|
|
repackedBlobs, err := repository.Repack(context.TODO(), repo, packs, blobs, nil)
|
2016-08-01 16:55:07 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-06-15 12:40:34 +00:00
|
|
|
|
|
|
|
for id := range repackedBlobs {
|
2020-08-16 09:16:38 +00:00
|
|
|
err = repo.Backend().Remove(context.TODO(), restic.Handle{Type: restic.PackFile, Name: id.String()})
|
2017-06-15 12:40:34 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
2016-08-01 16:55:07 +00:00
|
|
|
}
|
|
|
|
|
2016-09-03 11:34:04 +00:00
|
|
|
func saveIndex(t *testing.T, repo restic.Repository) {
|
2017-06-05 21:56:59 +00:00
|
|
|
if err := repo.SaveIndex(context.TODO()); err != nil {
|
2016-08-01 16:55:07 +00:00
|
|
|
t.Fatalf("repo.SaveIndex() %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-03 11:34:04 +00:00
|
|
|
func rebuildIndex(t *testing.T, repo restic.Repository) {
|
2017-06-15 13:03:05 +00:00
|
|
|
idx, _, err := index.New(context.TODO(), repo, restic.NewIDSet(), nil)
|
2017-03-01 12:44:56 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-01-21 16:25:36 +00:00
|
|
|
err = repo.List(context.TODO(), restic.IndexFile, func(id restic.ID, size int64) error {
|
2017-06-05 21:56:59 +00:00
|
|
|
h := restic.Handle{
|
2017-03-01 12:44:56 +00:00
|
|
|
Type: restic.IndexFile,
|
|
|
|
Name: id.String(),
|
2017-06-05 21:56:59 +00:00
|
|
|
}
|
2018-01-21 16:25:36 +00:00
|
|
|
return repo.Backend().Remove(context.TODO(), h)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2017-03-01 12:44:56 +00:00
|
|
|
}
|
|
|
|
|
2017-06-05 21:56:59 +00:00
|
|
|
_, err = idx.Save(context.TODO(), repo, nil)
|
2017-03-01 12:44:56 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2016-08-01 16:55:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-03 11:34:04 +00:00
|
|
|
func reloadIndex(t *testing.T, repo restic.Repository) {
|
2016-08-01 16:55:07 +00:00
|
|
|
repo.SetIndex(repository.NewMasterIndex())
|
2017-06-05 21:56:59 +00:00
|
|
|
if err := repo.LoadIndex(context.TODO()); err != nil {
|
2016-08-01 16:55:07 +00:00
|
|
|
t.Fatalf("error loading new index: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRepack(t *testing.T) {
|
|
|
|
repo, cleanup := repository.TestRepository(t)
|
|
|
|
defer cleanup()
|
|
|
|
|
2020-11-02 11:55:34 +00:00
|
|
|
seed := time.Now().UnixNano()
|
2018-01-23 20:41:39 +00:00
|
|
|
rand.Seed(seed)
|
|
|
|
t.Logf("rand seed is %v", seed)
|
|
|
|
|
2016-08-04 18:42:11 +00:00
|
|
|
createRandomBlobs(t, repo, 100, 0.7)
|
2016-08-01 16:55:07 +00:00
|
|
|
|
|
|
|
packsBefore := listPacks(t, repo)
|
|
|
|
|
|
|
|
// Running repack on empty ID sets should not do anything at all.
|
|
|
|
repack(t, repo, nil, nil)
|
|
|
|
|
|
|
|
packsAfter := listPacks(t, repo)
|
|
|
|
|
|
|
|
if !packsAfter.Equals(packsBefore) {
|
|
|
|
t.Fatalf("packs are not equal, Repack modified something. Before:\n %v\nAfter:\n %v",
|
|
|
|
packsBefore, packsAfter)
|
|
|
|
}
|
|
|
|
|
|
|
|
saveIndex(t, repo)
|
|
|
|
|
|
|
|
removeBlobs, keepBlobs := selectBlobs(t, repo, 0.2)
|
|
|
|
|
|
|
|
removePacks := findPacksForBlobs(t, repo, removeBlobs)
|
|
|
|
|
|
|
|
repack(t, repo, removePacks, keepBlobs)
|
|
|
|
rebuildIndex(t, repo)
|
|
|
|
reloadIndex(t, repo)
|
|
|
|
|
|
|
|
packsAfter = listPacks(t, repo)
|
|
|
|
for id := range removePacks {
|
|
|
|
if packsAfter.Has(id) {
|
|
|
|
t.Errorf("pack %v still present although it should have been repacked and removed", id.Str())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
idx := repo.Index()
|
2016-08-03 20:38:05 +00:00
|
|
|
|
|
|
|
for h := range keepBlobs {
|
2020-06-14 11:26:10 +00:00
|
|
|
list := idx.Lookup(h.ID, h.Type)
|
|
|
|
if len(list) == 0 {
|
2016-08-03 20:38:05 +00:00
|
|
|
t.Errorf("unable to find blob %v in repo", h.ID.Str())
|
|
|
|
continue
|
2016-08-01 16:55:07 +00:00
|
|
|
}
|
|
|
|
|
2016-08-03 20:38:05 +00:00
|
|
|
if len(list) != 1 {
|
|
|
|
t.Errorf("expected one pack in the list, got: %v", list)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
pb := list[0]
|
|
|
|
|
2016-08-01 16:55:07 +00:00
|
|
|
if removePacks.Has(pb.PackID) {
|
|
|
|
t.Errorf("lookup returned pack ID %v that should've been removed", pb.PackID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-03 20:38:05 +00:00
|
|
|
for h := range removeBlobs {
|
2020-06-14 11:26:10 +00:00
|
|
|
if _, found := repo.LookupBlobSize(h.ID, h.Type); found {
|
2016-08-03 20:38:05 +00:00
|
|
|
t.Errorf("blob %v still contained in the repo", h)
|
2016-08-01 16:55:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-31 15:04:48 +00:00
|
|
|
|
|
|
|
func TestRepackWrongBlob(t *testing.T) {
|
|
|
|
repo, cleanup := repository.TestRepository(t)
|
|
|
|
defer cleanup()
|
|
|
|
|
2020-11-02 11:55:34 +00:00
|
|
|
seed := time.Now().UnixNano()
|
2020-03-31 15:04:48 +00:00
|
|
|
rand.Seed(seed)
|
|
|
|
t.Logf("rand seed is %v", seed)
|
|
|
|
|
|
|
|
createRandomBlobs(t, repo, 5, 0.7)
|
|
|
|
createRandomWrongBlob(t, repo)
|
|
|
|
|
|
|
|
// just keep all blobs, but also rewrite every pack
|
|
|
|
_, keepBlobs := selectBlobs(t, repo, 0)
|
|
|
|
rewritePacks := findPacksForBlobs(t, repo, keepBlobs)
|
|
|
|
|
|
|
|
_, err := repository.Repack(context.TODO(), repo, rewritePacks, keepBlobs, nil)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected repack to fail but got no error")
|
|
|
|
}
|
2020-11-02 11:53:45 +00:00
|
|
|
t.Logf("found expected error: %v", err)
|
2020-03-31 15:04:48 +00:00
|
|
|
}
|