9c88801a12
Back in the before time, the best practices surrounding usage of Context weren't quite worked out. We defined our own type to make usage easier. As this packaged was used elsewhere, it make it more and more challenging to integrate with the forked `Context` type. Now that it is available in the standard library, we can just use that one directly. To make usage more consistent, we now use `dcontext` when referring to the distribution context package. Signed-off-by: Stephen J Day <stephen.day@docker.com>
325 lines
6.8 KiB
Go
325 lines
6.8 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"testing"
|
|
|
|
"github.com/docker/distribution"
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/distribution/registry/storage/cache/memory"
|
|
"github.com/docker/distribution/registry/storage/driver"
|
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
|
"github.com/docker/distribution/testutil"
|
|
"github.com/opencontainers/go-digest"
|
|
)
|
|
|
|
type setupEnv struct {
|
|
ctx context.Context
|
|
driver driver.StorageDriver
|
|
expected []string
|
|
registry distribution.Namespace
|
|
}
|
|
|
|
func setupFS(t *testing.T) *setupEnv {
|
|
d := inmemory.New()
|
|
ctx := context.Background()
|
|
registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect)
|
|
if err != nil {
|
|
t.Fatalf("error creating registry: %v", err)
|
|
}
|
|
|
|
repos := []string{
|
|
"foo/a",
|
|
"foo/b",
|
|
"foo-bar/a",
|
|
"bar/c",
|
|
"bar/d",
|
|
"bar/e",
|
|
"foo/d/in",
|
|
"foo-bar/b",
|
|
"test",
|
|
}
|
|
|
|
for _, repo := range repos {
|
|
makeRepo(ctx, t, repo, registry)
|
|
}
|
|
|
|
expected := []string{
|
|
"bar/c",
|
|
"bar/d",
|
|
"bar/e",
|
|
"foo/a",
|
|
"foo/b",
|
|
"foo/d/in",
|
|
"foo-bar/a",
|
|
"foo-bar/b",
|
|
"test",
|
|
}
|
|
|
|
return &setupEnv{
|
|
ctx: ctx,
|
|
driver: d,
|
|
expected: expected,
|
|
registry: registry,
|
|
}
|
|
}
|
|
|
|
func makeRepo(ctx context.Context, t *testing.T, name string, reg distribution.Namespace) {
|
|
named, err := reference.WithName(name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
repo, _ := reg.Repository(ctx, named)
|
|
manifests, _ := repo.Manifests(ctx)
|
|
|
|
layers, err := testutil.CreateRandomLayers(1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = testutil.UploadBlobs(repo, layers)
|
|
if err != nil {
|
|
t.Fatalf("failed to upload layers: %v", err)
|
|
}
|
|
|
|
getKeys := func(digests map[digest.Digest]io.ReadSeeker) (ds []digest.Digest) {
|
|
for d := range digests {
|
|
ds = append(ds, d)
|
|
}
|
|
return
|
|
}
|
|
|
|
manifest, err := testutil.MakeSchema1Manifest(getKeys(layers))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = manifests.Put(ctx, manifest)
|
|
if err != nil {
|
|
t.Fatalf("manifest upload failed: %v", err)
|
|
}
|
|
|
|
}
|
|
|
|
func TestCatalog(t *testing.T) {
|
|
env := setupFS(t)
|
|
|
|
p := make([]string, 50)
|
|
|
|
numFilled, err := env.registry.Repositories(env.ctx, p, "")
|
|
if numFilled != len(env.expected) {
|
|
t.Errorf("missing items in catalog")
|
|
}
|
|
|
|
if !testEq(p, env.expected, len(env.expected)) {
|
|
t.Errorf("Expected catalog repos err")
|
|
}
|
|
|
|
if err != io.EOF {
|
|
t.Errorf("Catalog has more values which we aren't expecting")
|
|
}
|
|
}
|
|
|
|
func TestCatalogInParts(t *testing.T) {
|
|
env := setupFS(t)
|
|
|
|
chunkLen := 3
|
|
p := make([]string, chunkLen)
|
|
|
|
numFilled, err := env.registry.Repositories(env.ctx, p, "")
|
|
if err == io.EOF || numFilled != len(p) {
|
|
t.Errorf("Expected more values in catalog")
|
|
}
|
|
|
|
if !testEq(p, env.expected[0:chunkLen], numFilled) {
|
|
t.Errorf("Expected catalog first chunk err")
|
|
}
|
|
|
|
lastRepo := p[len(p)-1]
|
|
numFilled, err = env.registry.Repositories(env.ctx, p, lastRepo)
|
|
|
|
if err == io.EOF || numFilled != len(p) {
|
|
t.Errorf("Expected more values in catalog")
|
|
}
|
|
|
|
if !testEq(p, env.expected[chunkLen:chunkLen*2], numFilled) {
|
|
t.Errorf("Expected catalog second chunk err")
|
|
}
|
|
|
|
lastRepo = p[len(p)-1]
|
|
numFilled, err = env.registry.Repositories(env.ctx, p, lastRepo)
|
|
|
|
if err != io.EOF || numFilled != len(p) {
|
|
t.Errorf("Expected end of catalog")
|
|
}
|
|
|
|
if !testEq(p, env.expected[chunkLen*2:chunkLen*3], numFilled) {
|
|
t.Errorf("Expected catalog third chunk err")
|
|
}
|
|
|
|
lastRepo = p[len(p)-1]
|
|
numFilled, err = env.registry.Repositories(env.ctx, p, lastRepo)
|
|
|
|
if err != io.EOF {
|
|
t.Errorf("Catalog has more values which we aren't expecting")
|
|
}
|
|
|
|
if numFilled != 0 {
|
|
t.Errorf("Expected catalog fourth chunk err")
|
|
}
|
|
}
|
|
|
|
func TestCatalogEnumerate(t *testing.T) {
|
|
env := setupFS(t)
|
|
|
|
var repos []string
|
|
repositoryEnumerator := env.registry.(distribution.RepositoryEnumerator)
|
|
err := repositoryEnumerator.Enumerate(env.ctx, func(repoName string) error {
|
|
repos = append(repos, repoName)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Expected catalog enumerate err")
|
|
}
|
|
|
|
if len(repos) != len(env.expected) {
|
|
t.Errorf("Expected catalog enumerate doesn't have correct number of values")
|
|
}
|
|
|
|
if !testEq(repos, env.expected, len(env.expected)) {
|
|
t.Errorf("Expected catalog enumerate not over all values")
|
|
}
|
|
}
|
|
|
|
func testEq(a, b []string, size int) bool {
|
|
for cnt := 0; cnt < size-1; cnt++ {
|
|
if a[cnt] != b[cnt] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func setupBadWalkEnv(t *testing.T) *setupEnv {
|
|
d := newBadListDriver()
|
|
ctx := context.Background()
|
|
registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect)
|
|
if err != nil {
|
|
t.Fatalf("error creating registry: %v", err)
|
|
}
|
|
|
|
return &setupEnv{
|
|
ctx: ctx,
|
|
driver: d,
|
|
registry: registry,
|
|
}
|
|
}
|
|
|
|
type badListDriver struct {
|
|
driver.StorageDriver
|
|
}
|
|
|
|
var _ driver.StorageDriver = &badListDriver{}
|
|
|
|
func newBadListDriver() *badListDriver {
|
|
return &badListDriver{StorageDriver: inmemory.New()}
|
|
}
|
|
|
|
func (d *badListDriver) List(ctx context.Context, path string) ([]string, error) {
|
|
return nil, fmt.Errorf("List error")
|
|
}
|
|
|
|
func TestCatalogWalkError(t *testing.T) {
|
|
env := setupBadWalkEnv(t)
|
|
p := make([]string, 1)
|
|
|
|
_, err := env.registry.Repositories(env.ctx, p, "")
|
|
if err == io.EOF {
|
|
t.Errorf("Expected catalog driver list error")
|
|
}
|
|
}
|
|
|
|
func BenchmarkPathCompareEqual(B *testing.B) {
|
|
B.StopTimer()
|
|
pp := randomPath(100)
|
|
// make a real copy
|
|
ppb := append([]byte{}, []byte(pp)...)
|
|
a, b := pp, string(ppb)
|
|
|
|
B.StartTimer()
|
|
for i := 0; i < B.N; i++ {
|
|
lessPath(a, b)
|
|
}
|
|
}
|
|
|
|
func BenchmarkPathCompareNotEqual(B *testing.B) {
|
|
B.StopTimer()
|
|
a, b := randomPath(100), randomPath(100)
|
|
B.StartTimer()
|
|
|
|
for i := 0; i < B.N; i++ {
|
|
lessPath(a, b)
|
|
}
|
|
}
|
|
|
|
func BenchmarkPathCompareNative(B *testing.B) {
|
|
B.StopTimer()
|
|
a, b := randomPath(100), randomPath(100)
|
|
B.StartTimer()
|
|
|
|
for i := 0; i < B.N; i++ {
|
|
c := a < b
|
|
c = c && false
|
|
}
|
|
}
|
|
|
|
func BenchmarkPathCompareNativeEqual(B *testing.B) {
|
|
B.StopTimer()
|
|
pp := randomPath(100)
|
|
a, b := pp, pp
|
|
B.StartTimer()
|
|
|
|
for i := 0; i < B.N; i++ {
|
|
c := a < b
|
|
c = c && false
|
|
}
|
|
}
|
|
|
|
var filenameChars = []byte("abcdefghijklmnopqrstuvwxyz0123456789")
|
|
var separatorChars = []byte("._-")
|
|
|
|
func randomPath(length int64) string {
|
|
path := "/"
|
|
for int64(len(path)) < length {
|
|
chunkLength := rand.Int63n(length-int64(len(path))) + 1
|
|
chunk := randomFilename(chunkLength)
|
|
path += chunk
|
|
remaining := length - int64(len(path))
|
|
if remaining == 1 {
|
|
path += randomFilename(1)
|
|
} else if remaining > 1 {
|
|
path += "/"
|
|
}
|
|
}
|
|
return path
|
|
}
|
|
|
|
func randomFilename(length int64) string {
|
|
b := make([]byte, length)
|
|
wasSeparator := true
|
|
for i := range b {
|
|
if !wasSeparator && i < len(b)-1 && rand.Intn(4) == 0 {
|
|
b[i] = separatorChars[rand.Intn(len(separatorChars))]
|
|
wasSeparator = true
|
|
} else {
|
|
b[i] = filenameChars[rand.Intn(len(filenameChars))]
|
|
wasSeparator = false
|
|
}
|
|
}
|
|
return string(b)
|
|
}
|