mirror of https://github.com/restic/restic.git
Recreate blob cache if missing (closes #104)
This commit is contained in:
parent
05f3e98ed9
commit
3c92c7e689
40
archiver.go
40
archiver.go
|
@ -76,21 +76,16 @@ func (arch *Archiver) Preload() error {
|
||||||
|
|
||||||
// TODO: track seen tree ids, load trees that aren't in the set
|
// TODO: track seen tree ids, load trees that aren't in the set
|
||||||
for _, id := range snapshots {
|
for _, id := range snapshots {
|
||||||
// try to load snapshot blobs from cache
|
m, err := arch.c.LoadMap(arch.s, id)
|
||||||
rd, err := arch.c.Load(backend.Snapshot, "blobs", id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("Archiver.Preload", "blobs for snapshot %v not cached: %v", id.Str(), err)
|
debug.Log("Archiver.Preload", "blobs for snapshot %v not cached: %v", id.Str(), err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
debug.Log("Archiver.Preload", "load cached blobs for snapshot %v", id.Str())
|
// build new cache
|
||||||
dec := json.NewDecoder(rd)
|
m, err = CacheSnapshotBlobs(arch.s, arch.c, id)
|
||||||
|
if err != nil {
|
||||||
m := &Map{}
|
debug.Log("Archiver.Preload", "unable to cache snapshot blobs for %v: %v", id.Str(), err)
|
||||||
err = dec.Decode(m)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
debug.Log("Archiver.Preload", "error loading cached blobs for snapshot %v: %v", id.Str(), err)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
arch.m.Merge(m)
|
arch.m.Merge(m)
|
||||||
|
@ -473,9 +468,9 @@ func (arch *Archiver) dirWorker(wg *sync.WaitGroup, p *Progress, done <-chan str
|
||||||
debug.Log("Archiver.dirWorker", "got tree node for %s: %v", node.path, node.blobs)
|
debug.Log("Archiver.dirWorker", "got tree node for %s: %v", node.path, node.blobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// also store blob in tree map
|
||||||
for _, blob := range node.blobs {
|
for _, blob := range node.blobs {
|
||||||
tree.Map.Insert(blob)
|
tree.Map.Insert(blob)
|
||||||
arch.m.Insert(blob)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,11 +756,12 @@ func (arch *Archiver) Snapshot(p *Progress, paths []string, pid backend.ID) (*Sn
|
||||||
|
|
||||||
// receive the top-level tree
|
// receive the top-level tree
|
||||||
root := (<-resCh).(*Node)
|
root := (<-resCh).(*Node)
|
||||||
debug.Log("Archiver.Snapshot", "root node received: %v", root.blobs[0])
|
blob := root.blobs[0]
|
||||||
sn.Tree = root.blobs[0]
|
debug.Log("Archiver.Snapshot", "root node received: %v", blob)
|
||||||
|
sn.Tree = blob
|
||||||
|
|
||||||
// save snapshot
|
// save snapshot
|
||||||
blob, err := arch.s.SaveJSON(backend.Snapshot, sn)
|
blob, err = arch.s.SaveJSON(backend.Snapshot, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -776,23 +772,13 @@ func (arch *Archiver) Snapshot(p *Progress, paths []string, pid backend.ID) (*Sn
|
||||||
debug.Log("Archiver.Snapshot", "saved snapshot %v", blob.Storage.Str())
|
debug.Log("Archiver.Snapshot", "saved snapshot %v", blob.Storage.Str())
|
||||||
|
|
||||||
// cache blobs
|
// cache blobs
|
||||||
wr, err := arch.c.Store(backend.Snapshot, "blobs", blob.Storage)
|
err = arch.c.StoreMap(sn.id, arch.m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("Archiver.Snapshot", "unable to cache blobs for snapshot %v: %v", blob.Storage.Str(), err)
|
debug.Log("Archiver.Snapshot", "unable to cache blobs for snapshot %v: %v", blob.Storage.Str(), err)
|
||||||
fmt.Fprintf(os.Stderr, "unable to cache blobs for snapshot %v: %v\n", blob.Storage.Str(), err)
|
fmt.Fprintf(os.Stderr, "unable to cache blobs for snapshot %v: %v\n", blob.Storage.Str(), err)
|
||||||
return sn, blob.Storage, nil
|
return sn, blob.Storage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := json.NewEncoder(wr)
|
|
||||||
err = enc.Encode(arch.m)
|
|
||||||
if err != nil {
|
|
||||||
debug.Log("Archiver.Snapshot", "error encoding map for snapshot %v: %v", blob.Storage.Str(), err)
|
|
||||||
} else {
|
|
||||||
debug.Log("Archiver.Snapshot", "cached %d blobs for snapshot %v", arch.m.Len(), blob.Storage.Str())
|
|
||||||
}
|
|
||||||
|
|
||||||
wr.Close()
|
|
||||||
|
|
||||||
return sn, blob.Storage, nil
|
return sn, blob.Storage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
79
cache.go
79
cache.go
|
@ -1,11 +1,13 @@
|
||||||
package restic
|
package restic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/restic/restic/backend"
|
"github.com/restic/restic/backend"
|
||||||
"github.com/restic/restic/debug"
|
"github.com/restic/restic/debug"
|
||||||
|
@ -203,3 +205,80 @@ func (c *Cache) filename(t backend.Type, subtype string, id backend.ID) (string,
|
||||||
|
|
||||||
return "", fmt.Errorf("cache not supported for type %v", t)
|
return "", fmt.Errorf("cache not supported for type %v", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// high-level functions
|
||||||
|
|
||||||
|
// CacheSnapshotBlobs creates a cache of all the blobs used within the
|
||||||
|
// snapshot. It collects all blobs from all trees and saves the resulting map
|
||||||
|
// to the cache and returns the map.
|
||||||
|
func CacheSnapshotBlobs(s Server, c *Cache, id backend.ID) (*Map, error) {
|
||||||
|
debug.Log("CacheSnapshotBlobs", "create cache for snapshot %v", id.Str())
|
||||||
|
|
||||||
|
sn, err := LoadSnapshot(s, id)
|
||||||
|
if err != nil {
|
||||||
|
debug.Log("CacheSnapshotBlobs", "unable to load snapshot %v: %v", id.Str(), err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := NewMap()
|
||||||
|
|
||||||
|
// add top-level node
|
||||||
|
m.Insert(sn.Tree)
|
||||||
|
|
||||||
|
// start walker
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
ch := make(chan WalkTreeJob)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
WalkTree(s, sn.Tree.Storage, nil, ch)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < maxConcurrencyPreload; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
for job := range ch {
|
||||||
|
if job.Tree == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
debug.Log("CacheSnapshotBlobs", "got job %v", job)
|
||||||
|
m.Merge(job.Tree.Map)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// save blob list for snapshot
|
||||||
|
return m, c.StoreMap(id, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) StoreMap(snid backend.ID, m *Map) error {
|
||||||
|
wr, err := c.Store(backend.Snapshot, "blobs", snid)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer wr.Close()
|
||||||
|
|
||||||
|
enc := json.NewEncoder(wr)
|
||||||
|
err = enc.Encode(m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) LoadMap(s Server, snid backend.ID) (*Map, error) {
|
||||||
|
rd, err := c.Load(backend.Snapshot, "blobs", snid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &Map{}
|
||||||
|
err = json.NewDecoder(rd).Decode(m)
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package restic_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic"
|
||||||
|
"github.com/restic/restic/backend"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
be := setupBackend(t)
|
||||||
|
defer teardownBackend(t, be)
|
||||||
|
key := setupKey(t, be, "geheim")
|
||||||
|
server := restic.NewServerWithKey(be, key)
|
||||||
|
|
||||||
|
cache, err := restic.NewCache(server)
|
||||||
|
ok(t, err)
|
||||||
|
|
||||||
|
arch, err := restic.NewArchiver(server)
|
||||||
|
ok(t, err)
|
||||||
|
|
||||||
|
// archive some files, this should automatically cache all blobs from the snapshot
|
||||||
|
_, id, err := arch.Snapshot(nil, []string{*benchArchiveDirectory}, nil)
|
||||||
|
|
||||||
|
// try to load map from cache
|
||||||
|
rd, err := cache.Load(backend.Snapshot, "blobs", id)
|
||||||
|
ok(t, err)
|
||||||
|
|
||||||
|
dec := json.NewDecoder(rd)
|
||||||
|
|
||||||
|
m := &restic.Map{}
|
||||||
|
err = dec.Decode(m)
|
||||||
|
ok(t, err)
|
||||||
|
|
||||||
|
// remove cached blob list
|
||||||
|
ok(t, cache.Purge(backend.Snapshot, "blobs", id))
|
||||||
|
|
||||||
|
// recreate cached blob list
|
||||||
|
m2, err := restic.CacheSnapshotBlobs(server, cache, id)
|
||||||
|
ok(t, err)
|
||||||
|
|
||||||
|
// compare maps
|
||||||
|
assert(t, m.Equals(m2), "Maps are not equal")
|
||||||
|
}
|
10
map.go
10
map.go
|
@ -124,15 +124,25 @@ func (bl *Map) StorageIDs() []backend.ID {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bl *Map) Equals(other *Map) bool {
|
func (bl *Map) Equals(other *Map) bool {
|
||||||
|
if bl == nil && other == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if bl == nil || other == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
bl.m.Lock()
|
bl.m.Lock()
|
||||||
defer bl.m.Unlock()
|
defer bl.m.Unlock()
|
||||||
|
|
||||||
if len(bl.list) != len(other.list) {
|
if len(bl.list) != len(other.list) {
|
||||||
|
debug.Log("Map.Equals", "length does not match: %d != %d", len(bl.list), len(other.list))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(bl.list); i++ {
|
for i := 0; i < len(bl.list); i++ {
|
||||||
if bl.list[i].Compare(other.list[i]) != 0 {
|
if bl.list[i].Compare(other.list[i]) != 0 {
|
||||||
|
debug.Log("Map.Equals", "entry %d does not match: %v != %v", i, bl.list[i], other.list[i])
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue