swarm/storage/localstore: add TestDB_gcIndex

This commit is contained in:
Janos Guljas 2018-12-06 09:54:04 +01:00
parent 35376d8b2b
commit e6a71961a5
3 changed files with 466 additions and 125 deletions

View file

@ -95,7 +95,6 @@ func testAccessors(t *testing.T, db *DB) {
for _, m := range []Mode{ for _, m := range []Mode{
ModeSyncing, ModeSyncing,
ModeUpload, ModeUpload,
ModeRequest,
} { } {
t.Run(ModeName(m), func(t *testing.T) { t.Run(ModeName(m), func(t *testing.T) {
a := db.Accessor(m) a := db.Accessor(m)
@ -136,9 +135,13 @@ func testAccessors(t *testing.T, db *DB) {
} }
}) })
// Access mode is a special as it does not store the chunk // Request and access modes are special as they do not store
// in the database. // chunks in the database.
t.Run(ModeName(modeAccess), func(t *testing.T) { for _, m := range []Mode{
ModeRequest,
modeAccess,
} {
t.Run(ModeName(m), func(t *testing.T) {
a := db.Accessor(ModeUpload) a := db.Accessor(ModeUpload)
want := generateRandomChunk() want := generateRandomChunk()
@ -149,7 +152,7 @@ func testAccessors(t *testing.T, db *DB) {
t.Fatal(err) t.Fatal(err)
} }
a = db.Accessor(modeAccess) a = db.Accessor(ModeRequest)
got, err := a.Get(context.Background(), want.Address()) got, err := a.Get(context.Background(), want.Address())
if err != nil { if err != nil {
@ -159,6 +162,7 @@ func testAccessors(t *testing.T, db *DB) {
t.Errorf("got chunk data %x, want %x", got.Data(), want.Data()) t.Errorf("got chunk data %x, want %x", got.Data(), want.Data())
} }
}) })
}
// Removal mode is a special case as it removes the chunk // Removal mode is a special case as it removes the chunk
// from the database. // from the database.

View file

@ -80,7 +80,7 @@ func (db *DB) access(mode Mode, item shed.IndexItem) (out shed.IndexItem, err er
} }
switch mode { switch mode {
case ModeRequest, modeAccess: case ModeRequest, modeAccess:
// update the access counter // update the access timestamp and fc index
return out, db.update(context.TODO(), mode, out) return out, db.update(context.TODO(), mode, out)
default: default:
// all other modes are not updating the index // all other modes are not updating the index
@ -147,7 +147,6 @@ func (db *DB) updateBatch(b *batch, mode Mode, item shed.IndexItem) (err error)
// put to indexes: retrieve, pull // put to indexes: retrieve, pull
item.StoreTimestamp = now() item.StoreTimestamp = now()
if db.useRetrievalCompositeIndex { if db.useRetrievalCompositeIndex {
item.AccessTimestamp = now()
db.retrievalCompositeIndex.PutInBatch(b.Batch, item) db.retrievalCompositeIndex.PutInBatch(b.Batch, item)
} else { } else {
db.retrievalDataIndex.PutInBatch(b.Batch, item) db.retrievalDataIndex.PutInBatch(b.Batch, item)
@ -159,7 +158,6 @@ func (db *DB) updateBatch(b *batch, mode Mode, item shed.IndexItem) (err error)
// put to indexes: retrieve, push, pull // put to indexes: retrieve, push, pull
item.StoreTimestamp = now() item.StoreTimestamp = now()
if db.useRetrievalCompositeIndex { if db.useRetrievalCompositeIndex {
item.AccessTimestamp = now()
db.retrievalCompositeIndex.PutInBatch(b.Batch, item) db.retrievalCompositeIndex.PutInBatch(b.Batch, item)
} else { } else {
db.retrievalDataIndex.PutInBatch(b.Batch, item) db.retrievalDataIndex.PutInBatch(b.Batch, item)
@ -169,49 +167,42 @@ func (db *DB) updateBatch(b *batch, mode Mode, item shed.IndexItem) (err error)
db.sizeCounter.IncInBatch(b.Batch) db.sizeCounter.IncInBatch(b.Batch)
case ModeRequest: case ModeRequest:
// put to indexes: retrieve, gc // update accessTimeStamp in retrieve, gc
item.StoreTimestamp = now()
item.AccessTimestamp = now()
if db.useRetrievalCompositeIndex {
db.retrievalCompositeIndex.PutInBatch(b.Batch, item)
} else {
db.retrievalDataIndex.PutInBatch(b.Batch, item)
db.retrievalAccessIndex.PutInBatch(b.Batch, item)
}
db.gcIndex.PutInBatch(b.Batch, item)
case ModeSynced:
// delete from push, insert to gc
item.StoreTimestamp = now()
// need to get access timestamp here as it is not
// provided by the access function, and it is not
// a property of a chunk provided to Accessor.Put.
if db.useRetrievalCompositeIndex { if db.useRetrievalCompositeIndex {
i, err := db.retrievalCompositeIndex.Get(item) // access timestap is already populated
switch err { // in the provided item, passed from access function.
case nil:
item.AccessTimestamp = i.AccessTimestamp
case leveldb.ErrNotFound:
item.AccessTimestamp = now()
default:
return err
}
} else { } else {
i, err := db.retrievalAccessIndex.Get(item) i, err := db.retrievalAccessIndex.Get(item)
switch err { switch err {
case nil: case nil:
item.AccessTimestamp = i.AccessTimestamp item.AccessTimestamp = i.AccessTimestamp
case leveldb.ErrNotFound: case leveldb.ErrNotFound:
item.AccessTimestamp = now() // no chunk accesses
default: default:
return err return err
} }
} }
db.pushIndex.DeleteInBatch(b.Batch, item) if item.AccessTimestamp == 0 {
// chunk is not yes synced
// do not add it to the gc index
return nil
}
// delete current entry from the gc index
db.gcIndex.DeleteInBatch(b.Batch, item)
// update access timestamp
item.AccessTimestamp = now()
// update retrieve access index
if db.useRetrievalCompositeIndex {
db.retrievalCompositeIndex.PutInBatch(b.Batch, item)
} else {
db.retrievalAccessIndex.PutInBatch(b.Batch, item)
}
// add new entry to gc index
db.gcIndex.PutInBatch(b.Batch, item) db.gcIndex.PutInBatch(b.Batch, item)
case modeAccess: case ModeSynced:
// update accessTimeStamp in retrieve, gc // delete from push, insert to gc
// need to get access timestamp here as it is not // need to get access timestamp here as it is not
// provided by the access function, and it is not // provided by the access function, and it is not
@ -219,20 +210,80 @@ func (db *DB) updateBatch(b *batch, mode Mode, item shed.IndexItem) (err error)
if db.useRetrievalCompositeIndex { if db.useRetrievalCompositeIndex {
i, err := db.retrievalCompositeIndex.Get(item) i, err := db.retrievalCompositeIndex.Get(item)
if err != nil { if err != nil {
if err == leveldb.ErrNotFound {
// chunk is not found,
// no need to update gc index
// just delete from the push index
// if it is there
db.pushIndex.DeleteInBatch(b.Batch, item)
return nil
}
return err return err
} }
item.AccessTimestamp = i.AccessTimestamp item.AccessTimestamp = i.AccessTimestamp
item.StoreTimestamp = i.StoreTimestamp
if item.AccessTimestamp == 0 {
// the chunk is not accessed before
// set access time for gc index
item.AccessTimestamp = now()
db.retrievalCompositeIndex.PutInBatch(b.Batch, item)
}
} else {
i, err := db.retrievalDataIndex.Get(item)
if err != nil {
if err == leveldb.ErrNotFound {
// chunk is not found,
// no need to update gc index
// just delete from the push index
// if it is there
db.pushIndex.DeleteInBatch(b.Batch, item)
return nil
}
return err
}
item.StoreTimestamp = i.StoreTimestamp
i, err = db.retrievalAccessIndex.Get(item)
switch err {
case nil:
item.AccessTimestamp = i.AccessTimestamp
db.gcIndex.DeleteInBatch(b.Batch, item)
case leveldb.ErrNotFound:
// the chunk is not accessed before
default:
return err
}
item.AccessTimestamp = now()
db.retrievalAccessIndex.PutInBatch(b.Batch, item)
}
db.pushIndex.DeleteInBatch(b.Batch, item)
db.gcIndex.PutInBatch(b.Batch, item)
// Q: modeAccess and ModeRequest are very similar, why do we need both?
case modeAccess:
// update accessTimeStamp in retrieve, pull, gc
if db.useRetrievalCompositeIndex {
// access timestap is already populated
// in the provided item, passed from access function.
} else { } else {
i, err := db.retrievalAccessIndex.Get(item) i, err := db.retrievalAccessIndex.Get(item)
switch err { switch err {
case nil: case nil:
item.AccessTimestamp = i.AccessTimestamp item.AccessTimestamp = i.AccessTimestamp
case leveldb.ErrNotFound: case leveldb.ErrNotFound:
item.AccessTimestamp = now() // no chunk accesses
default: default:
return err return err
} }
} }
// Q: why do we need to update this index?
db.pullIndex.PutInBatch(b.Batch, item)
if item.AccessTimestamp == 0 {
// chunk is not yes synced
// do not add it to the gc index
return nil
}
// delete current entry from the gc index // delete current entry from the gc index
db.gcIndex.DeleteInBatch(b.Batch, item) db.gcIndex.DeleteInBatch(b.Batch, item)
// update access timestamp // update access timestamp

View file

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"math/rand"
"sort" "sort"
"testing" "testing"
"time" "time"
@ -73,9 +74,9 @@ func testModeSyncingValues(t *testing.T, db *DB) {
wantSize++ wantSize++
t.Run("retrieve indexes", testRetrieveIndexesValues(db, chunk, wantTimestamp, wantTimestamp)) t.Run("retrieve indexes", newRetrieveIndexesTest(db, chunk, wantTimestamp, 0))
t.Run("pull index", testPullIndexValues(db, chunk, wantTimestamp, nil)) t.Run("pull index", newPullIndexTest(db, chunk, wantTimestamp, nil))
t.Run("size counter", testSizeCounter(db, wantSize)) t.Run("size counter", testSizeCounter(db, wantSize))
} }
@ -123,11 +124,11 @@ func testModeUploadValues(t *testing.T, db *DB) {
wantSize++ wantSize++
t.Run("retrieve indexes", testRetrieveIndexesValues(db, chunk, wantTimestamp, wantTimestamp)) t.Run("retrieve indexes", newRetrieveIndexesTest(db, chunk, wantTimestamp, 0))
t.Run("pull index", testPullIndexValues(db, chunk, wantTimestamp, nil)) t.Run("pull index", newPullIndexTest(db, chunk, wantTimestamp, nil))
t.Run("push index", testPushIndexValues(db, chunk, wantTimestamp, nil)) t.Run("push index", newPushIndexTest(db, chunk, wantTimestamp, nil))
t.Run("size counter", testSizeCounter(db, wantSize)) t.Run("size counter", testSizeCounter(db, wantSize))
} }
@ -153,14 +154,14 @@ func TestModeRequest_useRetrievalCompositeIndex(t *testing.T) {
// testModeRequestValues validates ModeRequest index values on the provided DB. // testModeRequestValues validates ModeRequest index values on the provided DB.
func testModeRequestValues(t *testing.T, db *DB) { func testModeRequestValues(t *testing.T, db *DB) {
a := db.Accessor(ModeRequest) a := db.Accessor(ModeUpload)
chunk := generateRandomChunk() chunk := generateRandomChunk()
wantTimestamp := time.Now().UTC().UnixNano() uploadTimestamp := time.Now().UTC().UnixNano()
defer func(n func() int64) { now = n }(now) defer func(n func() int64) { now = n }(now)
now = func() (t int64) { now = func() (t int64) {
return wantTimestamp return uploadTimestamp
} }
err := a.Put(context.Background(), chunk) err := a.Put(context.Background(), chunk)
@ -168,9 +169,79 @@ func testModeRequestValues(t *testing.T, db *DB) {
t.Fatal(err) t.Fatal(err)
} }
t.Run("retrieve indexes", testRetrieveIndexesValuesWithAccess(db, chunk, wantTimestamp, wantTimestamp)) a = db.Accessor(ModeRequest)
t.Run("gc index", testGCIndexValues(db, chunk, wantTimestamp, wantTimestamp)) t.Run("get unsynced", func(t *testing.T) {
got, err := a.Get(context.Background(), chunk.Address())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(chunk.Address(), got.Address()) {
t.Errorf("got chunk address %x, want %s", chunk.Address(), got.Address())
}
if !bytes.Equal(chunk.Data(), got.Data()) {
t.Errorf("got chunk data %x, want %s", chunk.Data(), got.Data())
}
t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, 0))
t.Run("gc index count", newIndexItemsCountTest(db.gcIndex, 0))
})
// set chunk to synced state
err = db.Accessor(ModeSynced).Put(context.Background(), chunk)
if err != nil {
t.Fatal(err)
}
t.Run("first get", func(t *testing.T) {
got, err := a.Get(context.Background(), chunk.Address())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(chunk.Address(), got.Address()) {
t.Errorf("got chunk address %x, want %s", chunk.Address(), got.Address())
}
if !bytes.Equal(chunk.Data(), got.Data()) {
t.Errorf("got chunk data %x, want %s", chunk.Data(), got.Data())
}
t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, uploadTimestamp))
t.Run("gc index", newGCIndexTest(db, chunk, uploadTimestamp, uploadTimestamp))
t.Run("gc index count", newIndexItemsCountTest(db.gcIndex, 1))
})
t.Run("second get", func(t *testing.T) {
accessTimestamp := time.Now().UTC().UnixNano()
now = func() (t int64) {
return accessTimestamp
}
got, err := a.Get(context.Background(), chunk.Address())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(chunk.Address(), got.Address()) {
t.Errorf("got chunk address %x, want %s", chunk.Address(), got.Address())
}
if !bytes.Equal(chunk.Data(), got.Data()) {
t.Errorf("got chunk data %x, want %s", chunk.Data(), got.Data())
}
t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, accessTimestamp))
t.Run("gc index", newGCIndexTest(db, chunk, uploadTimestamp, accessTimestamp))
t.Run("gc index count", newIndexItemsCountTest(db.gcIndex, 1))
})
} }
// TestModeSynced validates internal data operations and state // TestModeSynced validates internal data operations and state
@ -216,11 +287,11 @@ func testModeSyncedValues(t *testing.T, db *DB) {
t.Fatal(err) t.Fatal(err)
} }
t.Run("retrieve indexes", testRetrieveIndexesValues(db, chunk, wantTimestamp, wantTimestamp)) t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, wantTimestamp, wantTimestamp))
t.Run("push index", testPushIndexValues(db, chunk, wantTimestamp, leveldb.ErrNotFound)) t.Run("push index", newPushIndexTest(db, chunk, wantTimestamp, leveldb.ErrNotFound))
t.Run("gc index", testGCIndexValues(db, chunk, wantTimestamp, wantTimestamp)) t.Run("gc index", newGCIndexTest(db, chunk, wantTimestamp, wantTimestamp))
} }
// TestModeAccess validates internal data operations and state // TestModeAccess validates internal data operations and state
@ -261,6 +332,31 @@ func testModeAccessValues(t *testing.T, db *DB) {
a = db.Accessor(modeAccess) a = db.Accessor(modeAccess)
t.Run("get unsynced", func(t *testing.T) {
got, err := a.Get(context.Background(), chunk.Address())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(chunk.Address(), got.Address()) {
t.Errorf("got chunk address %x, want %s", chunk.Address(), got.Address())
}
if !bytes.Equal(chunk.Data(), got.Data()) {
t.Errorf("got chunk data %x, want %s", chunk.Data(), got.Data())
}
t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, 0))
t.Run("gc index count", newIndexItemsCountTest(db.gcIndex, 0))
})
// set chunk to synced state
err = db.Accessor(ModeSynced).Put(context.Background(), chunk)
if err != nil {
t.Fatal(err)
}
t.Run("first get", func(t *testing.T) { t.Run("first get", func(t *testing.T) {
got, err := a.Get(context.Background(), chunk.Address()) got, err := a.Get(context.Background(), chunk.Address())
if err != nil { if err != nil {
@ -275,11 +371,11 @@ func testModeAccessValues(t *testing.T, db *DB) {
t.Errorf("got chunk data %x, want %s", chunk.Data(), got.Data()) t.Errorf("got chunk data %x, want %s", chunk.Data(), got.Data())
} }
t.Run("retrieve indexes", testRetrieveIndexesValuesWithAccess(db, chunk, uploadTimestamp, uploadTimestamp)) t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, uploadTimestamp))
t.Run("gc index", testGCIndexValues(db, chunk, uploadTimestamp, uploadTimestamp)) t.Run("gc index", newGCIndexTest(db, chunk, uploadTimestamp, uploadTimestamp))
t.Run("gc index count", testIndexItemsCount(db.gcIndex, 1)) t.Run("gc index count", newIndexItemsCountTest(db.gcIndex, 1))
}) })
t.Run("second get", func(t *testing.T) { t.Run("second get", func(t *testing.T) {
@ -301,11 +397,11 @@ func testModeAccessValues(t *testing.T, db *DB) {
t.Errorf("got chunk data %x, want %s", chunk.Data(), got.Data()) t.Errorf("got chunk data %x, want %s", chunk.Data(), got.Data())
} }
t.Run("retrieve indexes", testRetrieveIndexesValuesWithAccess(db, chunk, uploadTimestamp, accessTimestamp)) t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, accessTimestamp))
t.Run("gc index", testGCIndexValues(db, chunk, uploadTimestamp, accessTimestamp)) t.Run("gc index", newGCIndexTest(db, chunk, uploadTimestamp, accessTimestamp))
t.Run("gc index count", testIndexItemsCount(db.gcIndex, 1)) t.Run("gc index count", newIndexItemsCountTest(db.gcIndex, 1))
}) })
} }
@ -360,28 +456,28 @@ func testModeRemovalValues(t *testing.T, db *DB) {
if err != wantErr { if err != wantErr {
t.Errorf("got error %v, want %v", err, wantErr) t.Errorf("got error %v, want %v", err, wantErr)
} }
t.Run("retrieve index count", testIndexItemsCount(db.retrievalCompositeIndex, 0)) t.Run("retrieve index count", newIndexItemsCountTest(db.retrievalCompositeIndex, 0))
} else { } else {
_, err := db.retrievalDataIndex.Get(addressToItem(chunk.Address())) _, err := db.retrievalDataIndex.Get(addressToItem(chunk.Address()))
if err != wantErr { if err != wantErr {
t.Errorf("got error %v, want %v", err, wantErr) t.Errorf("got error %v, want %v", err, wantErr)
} }
t.Run("retrieve data index count", testIndexItemsCount(db.retrievalDataIndex, 0)) t.Run("retrieve data index count", newIndexItemsCountTest(db.retrievalDataIndex, 0))
// access index should not be set // access index should not be set
_, err = db.retrievalAccessIndex.Get(addressToItem(chunk.Address())) _, err = db.retrievalAccessIndex.Get(addressToItem(chunk.Address()))
if err != wantErr { if err != wantErr {
t.Errorf("got error %v, want %v", err, wantErr) t.Errorf("got error %v, want %v", err, wantErr)
} }
t.Run("retrieve access index count", testIndexItemsCount(db.retrievalAccessIndex, 0)) t.Run("retrieve access index count", newIndexItemsCountTest(db.retrievalAccessIndex, 0))
} }
}) })
t.Run("pull index", testPullIndexValues(db, chunk, 0, leveldb.ErrNotFound)) t.Run("pull index", newPullIndexTest(db, chunk, 0, leveldb.ErrNotFound))
t.Run("pull index count", testIndexItemsCount(db.pullIndex, 0)) t.Run("pull index count", newIndexItemsCountTest(db.pullIndex, 0))
t.Run("gc index count", testIndexItemsCount(db.gcIndex, 0)) t.Run("gc index count", newIndexItemsCountTest(db.gcIndex, 0))
t.Run("size counter", testSizeCounter(db, wantSize)) t.Run("size counter", testSizeCounter(db, wantSize))
} }
@ -401,14 +497,7 @@ func TestDB_pullIndex(t *testing.T) {
chunkCount := 50 chunkCount := 50
// a wrapper around Chunk to keep chunks := make([]testIndexChunk, chunkCount)
// store timestamp for sorting
type testChunk struct {
storage.Chunk
storeTimestamp int64
}
chunks := make([]testChunk, chunkCount)
// upload random chunks // upload random chunks
for i := 0; i < chunkCount; i++ { for i := 0; i < chunkCount; i++ {
@ -419,7 +508,7 @@ func TestDB_pullIndex(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
chunks[i] = testChunk{ chunks[i] = testIndexChunk{
Chunk: chunk, Chunk: chunk,
// this timestamp is not the same as in // this timestamp is not the same as in
// the index, but given that uploads // the index, but given that uploads
@ -430,12 +519,7 @@ func TestDB_pullIndex(t *testing.T) {
} }
} }
// check if all chunks are stored testIndexItemsOrder(t, db.pullIndex, chunks, func(i, j int) (less bool) {
testIndexItemsCount(db.pullIndex, chunkCount)
// sort uploaded chunk is an expected pull index keys order
// "PO|StoredTimestamp|Hash"
sort.Slice(chunks, func(i, j int) (less bool) {
poi := storage.Proximity(db.baseKey, chunks[i].Address()) poi := storage.Proximity(db.baseKey, chunks[i].Address())
poj := storage.Proximity(db.baseKey, chunks[j].Address()) poj := storage.Proximity(db.baseKey, chunks[j].Address())
if poi < poj { if poi < poj {
@ -452,26 +536,199 @@ func TestDB_pullIndex(t *testing.T) {
} }
return bytes.Compare(chunks[i].Address(), chunks[j].Address()) == -1 return bytes.Compare(chunks[i].Address(), chunks[j].Address()) == -1
}) })
}
// iterate over all items func TestDB_gcIndex(t *testing.T) {
var cursor int db, cleanupFunc := newTestDB(t, nil)
err := db.pullIndex.IterateAll(func(item shed.IndexItem) (stop bool, err error) { defer cleanupFunc()
want := chunks[cursor].Address()
got := item.Address testDB_gcIndex(t, db)
if !bytes.Equal(got, want) { }
return true, fmt.Errorf("got address %x at position %v, want %x", got, cursor, want)
} func TestDB_gcIndex_useRetrievalCompositeIndex(t *testing.T) {
cursor++ db, cleanupFunc := newTestDB(t, &Options{UseRetrievalCompositeIndex: true})
return false, nil defer cleanupFunc()
})
testDB_gcIndex(t, db)
}
// testDB_gcIndex validates garbage collection index by uploading
// a chunk with and performing operations using synced, access and
// request modes.
func testDB_gcIndex(t *testing.T, db *DB) {
a := db.Accessor(ModeUpload)
chunkCount := 50
chunks := make([]testIndexChunk, chunkCount)
// upload random chunks
for i := 0; i < chunkCount; i++ {
chunk := generateRandomChunk()
err := a.Put(context.Background(), chunk)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
chunks[i] = testIndexChunk{
Chunk: chunk,
}
}
// check if all chunks are stored
newIndexItemsCountTest(db.pullIndex, chunkCount)(t)
// check that chunks are not collectable for garbage
newIndexItemsCountTest(db.gcIndex, 0)(t)
t.Run("access unsynced", func(t *testing.T) {
chunk := chunks[0]
a := db.Accessor(modeAccess)
_, err := a.Get(context.Background(), chunk.Address())
if err != nil {
t.Fatal(err)
}
// the chunk is not synced
// should not be in the garbace collection index
newIndexItemsCountTest(db.gcIndex, 0)(t)
})
t.Run("request unsynced", func(t *testing.T) {
chunk := chunks[1]
a := db.Accessor(ModeRequest)
_, err := a.Get(context.Background(), chunk.Address())
if err != nil {
t.Fatal(err)
}
// the chunk is not synced
// should not be in the garbace collection index
newIndexItemsCountTest(db.gcIndex, 0)(t)
})
t.Run("sync one chunk", func(t *testing.T) {
chunk := chunks[0]
a := db.Accessor(ModeSynced)
err := a.Put(context.Background(), chunk)
if err != nil {
t.Fatal(err)
}
// the chunk is synced and should be in gc index
newIndexItemsCountTest(db.gcIndex, 1)(t)
})
t.Run("sync all chunks", func(t *testing.T) {
a := db.Accessor(ModeSynced)
for i := range chunks {
err := a.Put(context.Background(), chunks[i])
if err != nil {
t.Fatal(err)
}
}
testIndexItemsOrder(t, db.gcIndex, chunks, nil)
})
t.Run("access one chunk", func(t *testing.T) {
a := db.Accessor(modeAccess)
i := 5
_, err := a.Get(context.Background(), chunks[i].Address())
if err != nil {
t.Fatal(err)
}
// move the chunk to the end of the expected gc
c := chunks[i]
chunks = append(chunks[:i], chunks[i+1:]...)
chunks = append(chunks, c)
testIndexItemsOrder(t, db.gcIndex, chunks, nil)
})
t.Run("request one chunk", func(t *testing.T) {
a := db.Accessor(ModeRequest)
i := 6
_, err := a.Get(context.Background(), chunks[i].Address())
if err != nil {
t.Fatal(err)
}
// move the chunk to the end of the expected gc
c := chunks[i]
chunks = append(chunks[:i], chunks[i+1:]...)
chunks = append(chunks, c)
testIndexItemsOrder(t, db.gcIndex, chunks, nil)
})
t.Run("random chunk access", func(t *testing.T) {
a := db.Accessor(modeAccess)
rand.Shuffle(len(chunks), func(i, j int) {
chunks[i], chunks[j] = chunks[j], chunks[i]
})
for _, chunk := range chunks {
_, err := a.Get(context.Background(), chunk.Address())
if err != nil {
t.Fatal(err)
}
}
testIndexItemsOrder(t, db.gcIndex, chunks, nil)
})
t.Run("random chunk request", func(t *testing.T) {
a := db.Accessor(ModeRequest)
rand.Shuffle(len(chunks), func(i, j int) {
chunks[i], chunks[j] = chunks[j], chunks[i]
})
for _, chunk := range chunks {
_, err := a.Get(context.Background(), chunk.Address())
if err != nil {
t.Fatal(err)
}
}
testIndexItemsOrder(t, db.gcIndex, chunks, nil)
})
t.Run("remove one chunk", func(t *testing.T) {
a := db.Accessor(modeRemoval)
i := 3
err := a.Put(context.Background(), chunks[i])
if err != nil {
t.Fatal(err)
}
// remove the chunk from the expected chunks in gc index
chunks = append(chunks[:i], chunks[i+1:]...)
testIndexItemsOrder(t, db.gcIndex, chunks, nil)
})
} }
// testRetrieveIndexesValues returns a test function that validates if the right // newRetrieveIndexesTest returns a test function that validates if the right
// chunk values are in the retrieval indexes. // chunk values are in the retrieval indexes.
func testRetrieveIndexesValues(db *DB, chunk storage.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) { func newRetrieveIndexesTest(db *DB, chunk storage.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
if db.useRetrievalCompositeIndex { if db.useRetrievalCompositeIndex {
item, err := db.retrievalCompositeIndex.Get(addressToItem(chunk.Address())) item, err := db.retrievalCompositeIndex.Get(addressToItem(chunk.Address()))
@ -496,9 +753,9 @@ func testRetrieveIndexesValues(db *DB, chunk storage.Chunk, storeTimestamp, acce
} }
} }
// testRetrieveIndexesValuesWithAccess returns a test function that validates if the right // newRetrieveIndexesTestWithAccess returns a test function that validates if the right
// chunk values are in the retrieval indexes when access time must be stored. // chunk values are in the retrieval indexes when access time must be stored.
func testRetrieveIndexesValuesWithAccess(db *DB, chunk storage.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) { func newRetrieveIndexesTestWithAccess(db *DB, chunk storage.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
if db.useRetrievalCompositeIndex { if db.useRetrievalCompositeIndex {
item, err := db.retrievalCompositeIndex.Get(addressToItem(chunk.Address())) item, err := db.retrievalCompositeIndex.Get(addressToItem(chunk.Address()))
@ -513,7 +770,7 @@ func testRetrieveIndexesValuesWithAccess(db *DB, chunk storage.Chunk, storeTimes
} }
validateItem(t, item, chunk.Address(), chunk.Data(), storeTimestamp, 0) validateItem(t, item, chunk.Address(), chunk.Data(), storeTimestamp, 0)
// access index should not be set if accessTimestamp > 0 {
item, err = db.retrievalAccessIndex.Get(addressToItem(chunk.Address())) item, err = db.retrievalAccessIndex.Get(addressToItem(chunk.Address()))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -521,11 +778,12 @@ func testRetrieveIndexesValuesWithAccess(db *DB, chunk storage.Chunk, storeTimes
validateItem(t, item, chunk.Address(), nil, 0, accessTimestamp) validateItem(t, item, chunk.Address(), nil, 0, accessTimestamp)
} }
} }
}
} }
// testPullIndexValues returns a test function that validates if the right // newPullIndexTest returns a test function that validates if the right
// chunk values are in the pull index. // chunk values are in the pull index.
func testPullIndexValues(db *DB, chunk storage.Chunk, storeTimestamp int64, wantError error) func(t *testing.T) { func newPullIndexTest(db *DB, chunk storage.Chunk, storeTimestamp int64, wantError error) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
item, err := db.pullIndex.Get(shed.IndexItem{ item, err := db.pullIndex.Get(shed.IndexItem{
Address: chunk.Address(), Address: chunk.Address(),
@ -540,9 +798,9 @@ func testPullIndexValues(db *DB, chunk storage.Chunk, storeTimestamp int64, want
} }
} }
// testPushIndexValues returns a test function that validates if the right // newPushIndexTest returns a test function that validates if the right
// chunk values are in the push index. // chunk values are in the push index.
func testPushIndexValues(db *DB, chunk storage.Chunk, storeTimestamp int64, wantError error) func(t *testing.T) { func newPushIndexTest(db *DB, chunk storage.Chunk, storeTimestamp int64, wantError error) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
item, err := db.pushIndex.Get(shed.IndexItem{ item, err := db.pushIndex.Get(shed.IndexItem{
Address: chunk.Address(), Address: chunk.Address(),
@ -557,9 +815,9 @@ func testPushIndexValues(db *DB, chunk storage.Chunk, storeTimestamp int64, want
} }
} }
// testGCIndexValues returns a test function that validates if the right // newGCIndexTest returns a test function that validates if the right
// chunk values are in the push index. // chunk values are in the push index.
func testGCIndexValues(db *DB, chunk storage.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) { func newGCIndexTest(db *DB, chunk storage.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
item, err := db.gcIndex.Get(shed.IndexItem{ item, err := db.gcIndex.Get(shed.IndexItem{
Address: chunk.Address(), Address: chunk.Address(),
@ -573,9 +831,9 @@ func testGCIndexValues(db *DB, chunk storage.Chunk, storeTimestamp, accessTimest
} }
} }
// testIndexItemsCount returns a test function that validates if // newIndexItemsCountTest returns a test function that validates if
// an index contains expected number of key/value pairs. // an index contains expected number of key/value pairs.
func testIndexItemsCount(i shed.Index, want int) func(t *testing.T) { func newIndexItemsCountTest(i shed.Index, want int) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
var c int var c int
i.IterateAll(func(item shed.IndexItem) (stop bool, err error) { i.IterateAll(func(item shed.IndexItem) (stop bool, err error) {
@ -588,6 +846,34 @@ func testIndexItemsCount(i shed.Index, want int) func(t *testing.T) {
} }
} }
type testIndexChunk struct {
storage.Chunk
storeTimestamp int64
accessTimestamp int64
}
func testIndexItemsOrder(t *testing.T, i shed.Index, chunks []testIndexChunk, sortFunc func(i, j int) (less bool)) {
newIndexItemsCountTest(i, len(chunks))(t)
if sortFunc != nil {
sort.Slice(chunks, sortFunc)
}
var cursor int
err := i.IterateAll(func(item shed.IndexItem) (stop bool, err error) {
want := chunks[cursor].Address()
got := item.Address
if !bytes.Equal(got, want) {
return true, fmt.Errorf("got address %x at position %v, want %x", got, cursor, want)
}
cursor++
return false, nil
})
if err != nil {
t.Fatal(err)
}
}
// testSizeCounter returns a test function that validates the expected // testSizeCounter returns a test function that validates the expected
// value from sizeCounter field. // value from sizeCounter field.
func testSizeCounter(db *DB, wantSize uint64) func(t *testing.T) { func testSizeCounter(db *DB, wantSize uint64) func(t *testing.T) {