improve proxycache and stats blobservers
improving proxycache
- added fuller sample config to the package documentation
- switched the stats caching from sorted.kv to the stats blobserver
- added a cleaning mechanism to evict the least recently used blobs
- implemented StatBlobs to actually inspect the local cache. It still
always consults the origin, but only for the blobs necessary after
giving the cache a 50ms headstart.
- logging a few errors that were previously ignored
- added tests modeled after the tests for the localdisk blobstore
- added a method to verify the cache, and call it on initialization
- added a strictStats option to always get stats from the origin
- filling in cacheBytes on initialization
improving stats blobserver
- implemented a few more of the blobserver interfaces, Enumerator and
Remover
- Fixed a bug(?) in ReceiveBlob that seemed to prevent it from actually
storing stats
- added a test
minor improvements include:
- blobserver/memory: allowing the memory blobserver to hold actually
infinite items, if desired
- blobserver: closing dest in the NoImpl blobserver, as required by the
BlobEnumerator interface
- storagetest: not closing dest leads to deadlock
- lru: max entries of 0 now means infinite (maybe do anything <0?)
- test: a helper function to create a random blob using a global random
source that is, by default, deterministic, to make test results more
consistent.
In the future, an improved BlobHub or similar interface could allow a
tighter feedback loop in providing cache consistency. i.e. the cache
could register with backend stores to be notified of content updates,
minimizing the time between backend changes and cache correction.
The proxycache will verify itself at startup, reporting an error if
any of its blobs do not exist in the backend storage or if the backend
storage has a different size for the content than the cache.
Fixes #443
Change-Id: I9ee1efd8c1d0eed49bb82930c2489a64122d3e00
2016-12-05 06:27:52 +00:00
|
|
|
/*
|
Rename import paths from camlistore.org to perkeep.org.
Part of the project renaming, issue #981.
After this, users will need to mv their $GOPATH/src/camlistore.org to
$GOPATH/src/perkeep.org. Sorry.
This doesn't yet rename the tools like camlistored, camput, camget,
camtool, etc.
Also, this only moves the lru package to internal. More will move to
internal later.
Also, this doesn't yet remove the "/pkg/" directory. That'll likely
happen later.
This updates some docs, but not all.
devcam test now passes again, even with Go 1.10 (which requires vet
checks are clean too). So a bunch of vet tests are fixed in this CL
too, and a bunch of other broken tests are now fixed (introduced from
the past week of merging the CL backlog).
Change-Id: If580db1691b5b99f8ed6195070789b1f44877dd4
2018-01-01 22:41:41 +00:00
|
|
|
Copyright 2016 The Perkeep Authors.
|
improve proxycache and stats blobservers
improving proxycache
- added fuller sample config to the package documentation
- switched the stats caching from sorted.kv to the stats blobserver
- added a cleaning mechanism to evict the least recently used blobs
- implemented StatBlobs to actually inspect the local cache. It still
always consults the origin, but only for the blobs necessary after
giving the cache a 50ms headstart.
- logging a few errors that were previously ignored
- added tests modeled after the tests for the localdisk blobstore
- added a method to verify the cache, and call it on initialization
- added a strictStats option to always get stats from the origin
- filling in cacheBytes on initialization
improving stats blobserver
- implemented a few more of the blobserver interfaces, Enumerator and
Remover
- Fixed a bug(?) in ReceiveBlob that seemed to prevent it from actually
storing stats
- added a test
minor improvements include:
- blobserver/memory: allowing the memory blobserver to hold actually
infinite items, if desired
- blobserver: closing dest in the NoImpl blobserver, as required by the
BlobEnumerator interface
- storagetest: not closing dest leads to deadlock
- lru: max entries of 0 now means infinite (maybe do anything <0?)
- test: a helper function to create a random blob using a global random
source that is, by default, deterministic, to make test results more
consistent.
In the future, an improved BlobHub or similar interface could allow a
tighter feedback loop in providing cache consistency. i.e. the cache
could register with backend stores to be notified of content updates,
minimizing the time between backend changes and cache correction.
The proxycache will verify itself at startup, reporting an error if
any of its blobs do not exist in the backend storage or if the backend
storage has a different size for the content than the cache.
Fixes #443
Change-Id: I9ee1efd8c1d0eed49bb82930c2489a64122d3e00
2016-12-05 06:27:52 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package proxycache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"go4.org/jsonconfig"
|
|
|
|
|
Rename import paths from camlistore.org to perkeep.org.
Part of the project renaming, issue #981.
After this, users will need to mv their $GOPATH/src/camlistore.org to
$GOPATH/src/perkeep.org. Sorry.
This doesn't yet rename the tools like camlistored, camput, camget,
camtool, etc.
Also, this only moves the lru package to internal. More will move to
internal later.
Also, this doesn't yet remove the "/pkg/" directory. That'll likely
happen later.
This updates some docs, but not all.
devcam test now passes again, even with Go 1.10 (which requires vet
checks are clean too). So a bunch of vet tests are fixed in this CL
too, and a bunch of other broken tests are now fixed (introduced from
the past week of merging the CL backlog).
Change-Id: If580db1691b5b99f8ed6195070789b1f44877dd4
2018-01-01 22:41:41 +00:00
|
|
|
"perkeep.org/pkg/blob"
|
|
|
|
"perkeep.org/pkg/blobserver"
|
|
|
|
"perkeep.org/pkg/blobserver/localdisk"
|
|
|
|
"perkeep.org/pkg/blobserver/memory"
|
|
|
|
"perkeep.org/pkg/blobserver/storagetest"
|
|
|
|
"perkeep.org/pkg/test"
|
improve proxycache and stats blobservers
improving proxycache
- added fuller sample config to the package documentation
- switched the stats caching from sorted.kv to the stats blobserver
- added a cleaning mechanism to evict the least recently used blobs
- implemented StatBlobs to actually inspect the local cache. It still
always consults the origin, but only for the blobs necessary after
giving the cache a 50ms headstart.
- logging a few errors that were previously ignored
- added tests modeled after the tests for the localdisk blobstore
- added a method to verify the cache, and call it on initialization
- added a strictStats option to always get stats from the origin
- filling in cacheBytes on initialization
improving stats blobserver
- implemented a few more of the blobserver interfaces, Enumerator and
Remover
- Fixed a bug(?) in ReceiveBlob that seemed to prevent it from actually
storing stats
- added a test
minor improvements include:
- blobserver/memory: allowing the memory blobserver to hold actually
infinite items, if desired
- blobserver: closing dest in the NoImpl blobserver, as required by the
BlobEnumerator interface
- storagetest: not closing dest leads to deadlock
- lru: max entries of 0 now means infinite (maybe do anything <0?)
- test: a helper function to create a random blob using a global random
source that is, by default, deterministic, to make test results more
consistent.
In the future, an improved BlobHub or similar interface could allow a
tighter feedback loop in providing cache consistency. i.e. the cache
could register with backend stores to be notified of content updates,
minimizing the time between backend changes and cache correction.
The proxycache will verify itself at startup, reporting an error if
any of its blobs do not exist in the backend storage or if the backend
storage has a different size for the content than the cache.
Fixes #443
Change-Id: I9ee1efd8c1d0eed49bb82930c2489a64122d3e00
2016-12-05 06:27:52 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func cleanUp(ds *localdisk.DiskStorage) {
|
|
|
|
err := os.RemoveAll(rootDir)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error removing cache (%s): %v", rootDir, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
epochLock sync.Mutex
|
|
|
|
rootEpoch = 0
|
|
|
|
rootDir string
|
|
|
|
)
|
|
|
|
|
|
|
|
func NewDiskStorage(t *testing.T) *localdisk.DiskStorage {
|
|
|
|
epochLock.Lock()
|
|
|
|
rootEpoch++
|
|
|
|
path := fmt.Sprintf("%s/camli-testroot-%d-%d", os.TempDir(), os.Getpid(), rootEpoch)
|
|
|
|
rootDir = path
|
|
|
|
epochLock.Unlock()
|
|
|
|
if err := os.Mkdir(path, 0755); err != nil {
|
|
|
|
t.Fatalf("Failed to create temp directory %q: %v", path, err)
|
|
|
|
}
|
|
|
|
ds, err := localdisk.New(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to run New: %v", err)
|
|
|
|
}
|
|
|
|
return ds
|
|
|
|
}
|
|
|
|
|
|
|
|
const cacheSize = 1 << 20
|
|
|
|
|
|
|
|
func NewProxiedDisk(t *testing.T) (*sto, *localdisk.DiskStorage) {
|
|
|
|
ds := NewDiskStorage(t)
|
|
|
|
return NewCache(cacheSize, memory.NewCache(cacheSize), ds).(*sto), ds
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEviction(t *testing.T) {
|
|
|
|
const blobsize = cacheSize / 6
|
|
|
|
px, ds := NewProxiedDisk(t)
|
|
|
|
defer cleanUp(ds)
|
|
|
|
|
|
|
|
tb := test.RandomBlob(t, blobsize)
|
|
|
|
tb.MustUpload(t, px)
|
|
|
|
test.RandomBlob(t, blobsize).MustUpload(t, px)
|
|
|
|
test.RandomBlob(t, blobsize).MustUpload(t, px)
|
|
|
|
test.RandomBlob(t, blobsize).MustUpload(t, px)
|
|
|
|
test.RandomBlob(t, blobsize).MustUpload(t, px)
|
|
|
|
test.RandomBlob(t, blobsize).MustUpload(t, px)
|
|
|
|
|
|
|
|
_, _, err := px.cache.Fetch(tb.BlobRef())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("ref should still be in the proxy:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
test.RandomBlob(t, blobsize).MustUpload(t, px)
|
|
|
|
_, _, err = px.cache.Fetch(tb.BlobRef())
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("ref should have been evicted from the proxy")
|
|
|
|
}
|
|
|
|
|
|
|
|
_, _, err = px.Fetch(tb.BlobRef())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("ref should be available via the proxy fetching from origin:", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestReceiveStat(t *testing.T) {
|
|
|
|
px, ds := NewProxiedDisk(t)
|
|
|
|
defer cleanUp(ds)
|
|
|
|
|
|
|
|
tb := &test.Blob{"Foo"}
|
|
|
|
tb.MustUpload(t, ds)
|
|
|
|
|
|
|
|
// get the stat via the cold proxycache
|
|
|
|
ch := make(chan blob.SizedRef, 0)
|
|
|
|
errch := make(chan error, 1)
|
|
|
|
go func() {
|
|
|
|
errch <- px.StatBlobs(ch, tb.BlobRefSlice())
|
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
got := 0
|
|
|
|
for sb := range ch {
|
|
|
|
got++
|
|
|
|
tb.AssertMatches(t, sb)
|
|
|
|
}
|
|
|
|
if err := <-errch; err != nil {
|
|
|
|
t.Fatalf("result from stat (cold cache): %v", err)
|
|
|
|
}
|
|
|
|
if got != 1 {
|
|
|
|
t.Fatalf("number stat results (cold cache), expected %d, got %d", 1, got)
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the stat via the warmed cache
|
|
|
|
px.origin = blobserver.NoImplStorage{} // force using the warmed cache
|
|
|
|
ch = make(chan blob.SizedRef, 0)
|
|
|
|
errch = make(chan error, 1)
|
|
|
|
go func() {
|
|
|
|
errch <- px.statsCache.StatBlobs(ch, tb.BlobRefSlice())
|
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
got = 0
|
|
|
|
for sb := range ch {
|
|
|
|
got++
|
|
|
|
tb.AssertMatches(t, sb)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := <-errch; err != nil {
|
|
|
|
t.Fatalf("result from stat (warm cache): %v", err)
|
|
|
|
}
|
|
|
|
if got != 1 {
|
|
|
|
t.Fatalf("number stat results (warm cache), expected %d, got %d", 1, got)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMultiStat(t *testing.T) {
|
|
|
|
px, ds := NewProxiedDisk(t)
|
|
|
|
defer cleanUp(ds)
|
|
|
|
|
|
|
|
blobfoo := &test.Blob{"foo"}
|
|
|
|
blobbar := &test.Blob{"bar!"}
|
|
|
|
blobfoo.MustUpload(t, ds)
|
|
|
|
blobbar.MustUpload(t, ds)
|
|
|
|
|
|
|
|
need := make(map[blob.Ref]bool)
|
|
|
|
need[blobfoo.BlobRef()] = true
|
|
|
|
need[blobbar.BlobRef()] = true
|
|
|
|
|
|
|
|
blobs := []blob.Ref{blobfoo.BlobRef(), blobbar.BlobRef()}
|
|
|
|
|
|
|
|
ch := make(chan blob.SizedRef, 0)
|
|
|
|
errch := make(chan error, 1)
|
|
|
|
go func() {
|
|
|
|
errch <- px.StatBlobs(ch, blobs)
|
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
got := 0
|
|
|
|
for sb := range ch {
|
|
|
|
got++
|
|
|
|
if !need[sb.Ref] {
|
|
|
|
t.Errorf("didn't need %s", sb.Ref)
|
|
|
|
}
|
|
|
|
delete(need, sb.Ref)
|
|
|
|
}
|
|
|
|
if want := 2; got != want {
|
|
|
|
t.Errorf("number stats = %d; want %d", got, want)
|
|
|
|
}
|
|
|
|
if err := <-errch; err != nil {
|
|
|
|
t.Errorf("StatBlobs: %v", err)
|
|
|
|
}
|
|
|
|
if len(need) != 0 {
|
|
|
|
t.Errorf("Not all stat results returned; still need %d", len(need))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMissingGetReturnsNoEnt(t *testing.T) {
|
|
|
|
px, ds := NewProxiedDisk(t)
|
|
|
|
defer cleanUp(ds)
|
|
|
|
foo := &test.Blob{"foo"}
|
|
|
|
|
|
|
|
blob, _, err := px.Fetch(foo.BlobRef())
|
|
|
|
if err != os.ErrNotExist {
|
|
|
|
t.Errorf("expected ErrNotExist; got %v", err)
|
|
|
|
}
|
|
|
|
if blob != nil {
|
|
|
|
t.Errorf("expected nil blob; got a value")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProxyCache(t *testing.T) {
|
|
|
|
px, ds := NewProxiedDisk(t)
|
|
|
|
storagetest.Test(t, func(t *testing.T) (blobserver.Storage, func()) {
|
|
|
|
return px, func() {}
|
|
|
|
})
|
|
|
|
px.origin = memory.NewCache(0)
|
|
|
|
storagetest.Test(t, func(t *testing.T) (blobserver.Storage, func()) {
|
|
|
|
return px, func() { cleanUp(ds) }
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestVerify(t *testing.T) {
|
|
|
|
px, ds := NewProxiedDisk(t)
|
|
|
|
defer cleanUp(ds)
|
|
|
|
|
|
|
|
blobfoo := &test.Blob{"foo"}
|
|
|
|
blobbar := &test.Blob{"bar!"}
|
|
|
|
blobbaz := &test.Blob{"baz~!"} // not going to upload this one
|
|
|
|
|
|
|
|
blobfoo.MustUpload(t, px)
|
|
|
|
blobbar.MustUpload(t, px)
|
|
|
|
|
|
|
|
err := px.verifyCache(blobfoo.BlobRefSlice())
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("unexpected error verifying blobfoo: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = px.verifyCache(blobbaz.BlobRefSlice())
|
|
|
|
if err, ok := err.(CacheMissingRefError); !ok {
|
|
|
|
t.Errorf("expected error CacheMissingRefError verifying blobbaz")
|
|
|
|
} else if err.Ref != blobbaz.BlobRef() {
|
|
|
|
t.Errorf("error ref did not match blobbaz ref")
|
|
|
|
}
|
|
|
|
|
|
|
|
px.origin = memory.NewCache(0)
|
|
|
|
err = px.verifyCache(nil)
|
|
|
|
if err == nil {
|
|
|
|
t.Error("expected some errors from verifyCache after we messed it up")
|
|
|
|
}
|
|
|
|
t.Log(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConfig(t *testing.T) {
|
|
|
|
const maxBytes = 1 << 5
|
|
|
|
px, ds := NewProxiedDisk(t)
|
|
|
|
|
|
|
|
ld := test.NewLoader()
|
|
|
|
ld.SetStorage("origin", ds)
|
|
|
|
ld.SetStorage("cache", px)
|
|
|
|
|
|
|
|
cache, err := newFromConfig(ld, jsonconfig.Obj{
|
|
|
|
"origin": "origin",
|
|
|
|
"cache": "cache",
|
|
|
|
"maxCacheBytes": float64(maxBytes),
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cache.(*sto).maxCacheBytes != maxBytes {
|
|
|
|
t.Fatalf("incorrectly read maxCacheBytes. saw: %d expected: %d",
|
|
|
|
cache.(*sto).maxCacheBytes, maxBytes)
|
|
|
|
}
|
|
|
|
}
|