58232e50cf
The current implementation of digest.FromBytes returns an error. This error can never be non-nil, but its presence in the function signature means each call site needs error handling code for an error that is always nil. I verified that none of the hash.Hash implementations in the standard library can return an error on Write. Nor can any of the hash.Hash implementations vendored in distribution. This commit changes digest.FromBytes not to return an error. If Write returns an error, it will panic, but as discussed above, this should never happen. This commit also avoids using a bytes.Reader to feed data into the hash function in FromBytes. This makes the hypothetical case that would panic a bit more explicit, and should also be more performant. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
243 lines
6.3 KiB
Go
243 lines
6.3 KiB
Go
package proxy
|
|
|
|
import (
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/docker/distribution"
|
|
"github.com/docker/distribution/context"
|
|
"github.com/docker/distribution/digest"
|
|
"github.com/docker/distribution/manifest"
|
|
"github.com/docker/distribution/manifest/schema1"
|
|
"github.com/docker/distribution/registry/proxy/scheduler"
|
|
"github.com/docker/distribution/registry/storage"
|
|
"github.com/docker/distribution/registry/storage/cache/memory"
|
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
|
"github.com/docker/distribution/testutil"
|
|
"github.com/docker/libtrust"
|
|
)
|
|
|
|
type statsManifest struct {
|
|
manifests distribution.ManifestService
|
|
stats map[string]int
|
|
}
|
|
|
|
type manifestStoreTestEnv struct {
|
|
manifestDigest digest.Digest // digest of the signed manifest in the local storage
|
|
manifests proxyManifestStore
|
|
}
|
|
|
|
func (te manifestStoreTestEnv) LocalStats() *map[string]int {
|
|
ls := te.manifests.localManifests.(statsManifest).stats
|
|
return &ls
|
|
}
|
|
|
|
func (te manifestStoreTestEnv) RemoteStats() *map[string]int {
|
|
rs := te.manifests.remoteManifests.(statsManifest).stats
|
|
return &rs
|
|
}
|
|
|
|
func (sm statsManifest) Delete(dgst digest.Digest) error {
|
|
sm.stats["delete"]++
|
|
return sm.manifests.Delete(dgst)
|
|
}
|
|
|
|
func (sm statsManifest) Exists(dgst digest.Digest) (bool, error) {
|
|
sm.stats["exists"]++
|
|
return sm.manifests.Exists(dgst)
|
|
}
|
|
|
|
func (sm statsManifest) ExistsByTag(tag string) (bool, error) {
|
|
sm.stats["existbytag"]++
|
|
return sm.manifests.ExistsByTag(tag)
|
|
}
|
|
|
|
func (sm statsManifest) Get(dgst digest.Digest) (*schema1.SignedManifest, error) {
|
|
sm.stats["get"]++
|
|
return sm.manifests.Get(dgst)
|
|
}
|
|
|
|
func (sm statsManifest) GetByTag(tag string, options ...distribution.ManifestServiceOption) (*schema1.SignedManifest, error) {
|
|
sm.stats["getbytag"]++
|
|
return sm.manifests.GetByTag(tag, options...)
|
|
}
|
|
|
|
func (sm statsManifest) Put(manifest *schema1.SignedManifest) error {
|
|
sm.stats["put"]++
|
|
return sm.manifests.Put(manifest)
|
|
}
|
|
|
|
func (sm statsManifest) Tags() ([]string, error) {
|
|
sm.stats["tags"]++
|
|
return sm.manifests.Tags()
|
|
}
|
|
|
|
func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestEnv {
|
|
ctx := context.Background()
|
|
truthRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()))
|
|
if err != nil {
|
|
t.Fatalf("error creating registry: %v", err)
|
|
}
|
|
truthRepo, err := truthRegistry.Repository(ctx, name)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error getting repo: %v", err)
|
|
}
|
|
tr, err := truthRepo.Manifests(ctx)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
truthManifests := statsManifest{
|
|
manifests: tr,
|
|
stats: make(map[string]int),
|
|
}
|
|
|
|
manifestDigest, err := populateRepo(t, ctx, truthRepo, name, tag)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
localRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableRedirect, storage.DisableDigestResumption)
|
|
if err != nil {
|
|
t.Fatalf("error creating registry: %v", err)
|
|
}
|
|
localRepo, err := localRegistry.Repository(ctx, name)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error getting repo: %v", err)
|
|
}
|
|
lr, err := localRepo.Manifests(ctx)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
|
|
localManifests := statsManifest{
|
|
manifests: lr,
|
|
stats: make(map[string]int),
|
|
}
|
|
|
|
s := scheduler.New(ctx, inmemory.New(), "/scheduler-state.json")
|
|
return &manifestStoreTestEnv{
|
|
manifestDigest: manifestDigest,
|
|
manifests: proxyManifestStore{
|
|
ctx: ctx,
|
|
localManifests: localManifests,
|
|
remoteManifests: truthManifests,
|
|
scheduler: s,
|
|
},
|
|
}
|
|
}
|
|
|
|
func populateRepo(t *testing.T, ctx context.Context, repository distribution.Repository, name, tag string) (digest.Digest, error) {
|
|
m := schema1.Manifest{
|
|
Versioned: manifest.Versioned{
|
|
SchemaVersion: 1,
|
|
},
|
|
Name: name,
|
|
Tag: tag,
|
|
}
|
|
|
|
for i := 0; i < 2; i++ {
|
|
wr, err := repository.Blobs(ctx).Create(ctx)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error creating test upload: %v", err)
|
|
}
|
|
|
|
rs, ts, err := testutil.CreateRandomTarFile()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error generating test layer file")
|
|
}
|
|
dgst := digest.Digest(ts)
|
|
if _, err := io.Copy(wr, rs); err != nil {
|
|
t.Fatalf("unexpected error copying to upload: %v", err)
|
|
}
|
|
|
|
if _, err := wr.Commit(ctx, distribution.Descriptor{Digest: dgst}); err != nil {
|
|
t.Fatalf("unexpected error finishing upload: %v", err)
|
|
}
|
|
}
|
|
|
|
pk, err := libtrust.GenerateECP256PrivateKey()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error generating private key: %v", err)
|
|
}
|
|
|
|
sm, err := schema1.Sign(&m, pk)
|
|
if err != nil {
|
|
t.Fatalf("error signing manifest: %v", err)
|
|
}
|
|
|
|
ms, err := repository.Manifests(ctx)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
ms.Put(sm)
|
|
if err != nil {
|
|
t.Fatalf("unexpected errors putting manifest: %v", err)
|
|
}
|
|
pl, err := sm.Payload()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return digest.FromBytes(pl), nil
|
|
}
|
|
|
|
// TestProxyManifests contains basic acceptance tests
|
|
// for the pull-through behavior
|
|
func TestProxyManifests(t *testing.T) {
|
|
name := "foo/bar"
|
|
env := newManifestStoreTestEnv(t, name, "latest")
|
|
|
|
localStats := env.LocalStats()
|
|
remoteStats := env.RemoteStats()
|
|
|
|
// Stat - must check local and remote
|
|
exists, err := env.manifests.ExistsByTag("latest")
|
|
if err != nil {
|
|
t.Fatalf("Error checking existance")
|
|
}
|
|
if !exists {
|
|
t.Errorf("Unexpected non-existant manifest")
|
|
}
|
|
|
|
if (*localStats)["existbytag"] != 1 && (*remoteStats)["existbytag"] != 1 {
|
|
t.Errorf("Unexpected exists count")
|
|
}
|
|
|
|
// Get - should succeed and pull manifest into local
|
|
_, err = env.manifests.Get(env.manifestDigest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if (*localStats)["get"] != 1 && (*remoteStats)["get"] != 1 {
|
|
t.Errorf("Unexpected get count")
|
|
}
|
|
|
|
if (*localStats)["put"] != 1 {
|
|
t.Errorf("Expected local put")
|
|
}
|
|
|
|
// Stat - should only go to local
|
|
exists, err = env.manifests.ExistsByTag("latest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !exists {
|
|
t.Errorf("Unexpected non-existant manifest")
|
|
}
|
|
|
|
if (*localStats)["existbytag"] != 2 && (*remoteStats)["existbytag"] != 1 {
|
|
t.Errorf("Unexpected exists count")
|
|
|
|
}
|
|
|
|
// Get - should get from remote, to test freshness
|
|
_, err = env.manifests.Get(env.manifestDigest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if (*remoteStats)["get"] != 2 && (*remoteStats)["existsbytag"] != 1 && (*localStats)["put"] != 1 {
|
|
t.Errorf("Unexpected get count")
|
|
}
|
|
|
|
}
|