1 package storage
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "math/rand"
8 "testing"
9
10 "github.com/distribution/reference"
11 "github.com/docker/distribution"
12 "github.com/docker/distribution/registry/storage/cache/memory"
13 "github.com/docker/distribution/registry/storage/driver"
14 "github.com/docker/distribution/registry/storage/driver/inmemory"
15 "github.com/docker/distribution/testutil"
16 "github.com/opencontainers/go-digest"
17 )
18
19 type setupEnv struct {
20 ctx context.Context
21 driver driver.StorageDriver
22 expected []string
23 registry distribution.Namespace
24 }
25
26 func setupFS(t *testing.T) *setupEnv {
27 d := inmemory.New()
28 ctx := context.Background()
29 registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect, EnableSchema1)
30 if err != nil {
31 t.Fatalf("error creating registry: %v", err)
32 }
33
34 repos := []string{
35 "foo/a",
36 "foo/b",
37 "foo-bar/a",
38 "bar/c",
39 "bar/d",
40 "bar/e",
41 "foo/d/in",
42 "foo-bar/b",
43 "test",
44 }
45
46 for _, repo := range repos {
47 makeRepo(ctx, t, repo, registry)
48 }
49
50 expected := []string{
51 "bar/c",
52 "bar/d",
53 "bar/e",
54 "foo/a",
55 "foo/b",
56 "foo/d/in",
57 "foo-bar/a",
58 "foo-bar/b",
59 "test",
60 }
61
62 return &setupEnv{
63 ctx: ctx,
64 driver: d,
65 expected: expected,
66 registry: registry,
67 }
68 }
69
70 func makeRepo(ctx context.Context, t *testing.T, name string, reg distribution.Namespace) {
71 named, err := reference.WithName(name)
72 if err != nil {
73 t.Fatal(err)
74 }
75
76 repo, _ := reg.Repository(ctx, named)
77 manifests, _ := repo.Manifests(ctx)
78
79 layers, err := testutil.CreateRandomLayers(1)
80 if err != nil {
81 t.Fatal(err)
82 }
83
84 err = testutil.UploadBlobs(repo, layers)
85 if err != nil {
86 t.Fatalf("failed to upload layers: %v", err)
87 }
88
89 getKeys := func(digests map[digest.Digest]io.ReadSeeker) (ds []digest.Digest) {
90 for d := range digests {
91 ds = append(ds, d)
92 }
93 return
94 }
95
96 manifest, err := testutil.MakeSchema1Manifest(getKeys(layers))
97 if err != nil {
98 t.Fatal(err)
99 }
100
101 _, err = manifests.Put(ctx, manifest)
102 if err != nil {
103 t.Fatalf("manifest upload failed: %v", err)
104 }
105
106 }
107
108 func TestCatalog(t *testing.T) {
109 env := setupFS(t)
110
111 p := make([]string, 50)
112
113 numFilled, err := env.registry.Repositories(env.ctx, p, "")
114 if numFilled != len(env.expected) {
115 t.Errorf("missing items in catalog")
116 }
117
118 if !testEq(p, env.expected, len(env.expected)) {
119 t.Errorf("Expected catalog repos err")
120 }
121
122 if err != io.EOF {
123 t.Errorf("Catalog has more values which we aren't expecting")
124 }
125 }
126
127 func TestCatalogInParts(t *testing.T) {
128 env := setupFS(t)
129
130 chunkLen := 3
131 p := make([]string, chunkLen)
132
133 numFilled, err := env.registry.Repositories(env.ctx, p, "")
134 if err == io.EOF || numFilled != len(p) {
135 t.Errorf("Expected more values in catalog")
136 }
137
138 if !testEq(p, env.expected[0:chunkLen], numFilled) {
139 t.Errorf("Expected catalog first chunk err")
140 }
141
142 lastRepo := p[len(p)-1]
143 numFilled, err = env.registry.Repositories(env.ctx, p, lastRepo)
144
145 if err == io.EOF || numFilled != len(p) {
146 t.Errorf("Expected more values in catalog")
147 }
148
149 if !testEq(p, env.expected[chunkLen:chunkLen*2], numFilled) {
150 t.Errorf("Expected catalog second chunk err")
151 }
152
153 lastRepo = p[len(p)-1]
154 numFilled, err = env.registry.Repositories(env.ctx, p, lastRepo)
155
156 if err != io.EOF || numFilled != len(p) {
157 t.Errorf("Expected end of catalog")
158 }
159
160 if !testEq(p, env.expected[chunkLen*2:chunkLen*3], numFilled) {
161 t.Errorf("Expected catalog third chunk err")
162 }
163
164 lastRepo = p[len(p)-1]
165 numFilled, err = env.registry.Repositories(env.ctx, p, lastRepo)
166
167 if err != io.EOF {
168 t.Errorf("Catalog has more values which we aren't expecting")
169 }
170
171 if numFilled != 0 {
172 t.Errorf("Expected catalog fourth chunk err")
173 }
174 }
175
176 func TestCatalogEnumerate(t *testing.T) {
177 env := setupFS(t)
178
179 var repos []string
180 repositoryEnumerator := env.registry.(distribution.RepositoryEnumerator)
181 err := repositoryEnumerator.Enumerate(env.ctx, func(repoName string) error {
182 repos = append(repos, repoName)
183 return nil
184 })
185 if err != nil {
186 t.Errorf("Expected catalog enumerate err")
187 }
188
189 if len(repos) != len(env.expected) {
190 t.Errorf("Expected catalog enumerate doesn't have correct number of values")
191 }
192
193 if !testEq(repos, env.expected, len(env.expected)) {
194 t.Errorf("Expected catalog enumerate not over all values")
195 }
196 }
197
198 func testEq(a, b []string, size int) bool {
199 for cnt := 0; cnt < size-1; cnt++ {
200 if a[cnt] != b[cnt] {
201 return false
202 }
203 }
204 return true
205 }
206
207 func setupBadWalkEnv(t *testing.T) *setupEnv {
208 d := newBadListDriver()
209 ctx := context.Background()
210 registry, err := NewRegistry(ctx, d, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect, EnableSchema1)
211 if err != nil {
212 t.Fatalf("error creating registry: %v", err)
213 }
214
215 return &setupEnv{
216 ctx: ctx,
217 driver: d,
218 registry: registry,
219 }
220 }
221
222 type badListDriver struct {
223 driver.StorageDriver
224 }
225
226 var _ driver.StorageDriver = &badListDriver{}
227
228 func newBadListDriver() *badListDriver {
229 return &badListDriver{StorageDriver: inmemory.New()}
230 }
231
232 func (d *badListDriver) List(ctx context.Context, path string) ([]string, error) {
233 return nil, fmt.Errorf("List error")
234 }
235
236 func TestCatalogWalkError(t *testing.T) {
237 env := setupBadWalkEnv(t)
238 p := make([]string, 1)
239
240 _, err := env.registry.Repositories(env.ctx, p, "")
241 if err == io.EOF {
242 t.Errorf("Expected catalog driver list error")
243 }
244 }
245
246 func BenchmarkPathCompareEqual(B *testing.B) {
247 B.StopTimer()
248 pp := randomPath(100)
249
250 ppb := append([]byte{}, []byte(pp)...)
251 a, b := pp, string(ppb)
252
253 B.StartTimer()
254 for i := 0; i < B.N; i++ {
255 lessPath(a, b)
256 }
257 }
258
259 func BenchmarkPathCompareNotEqual(B *testing.B) {
260 B.StopTimer()
261 a, b := randomPath(100), randomPath(100)
262 B.StartTimer()
263
264 for i := 0; i < B.N; i++ {
265 lessPath(a, b)
266 }
267 }
268
269 func BenchmarkPathCompareNative(B *testing.B) {
270 B.StopTimer()
271 a, b := randomPath(100), randomPath(100)
272 B.StartTimer()
273
274 for i := 0; i < B.N; i++ {
275 c := a < b
276 _ = c && false
277 }
278 }
279
280 func BenchmarkPathCompareNativeEqual(B *testing.B) {
281 B.StopTimer()
282 pp := randomPath(100)
283 a, b := pp, pp
284 B.StartTimer()
285
286 for i := 0; i < B.N; i++ {
287 c := a < b
288 _ = c && false
289 }
290 }
291
292 var filenameChars = []byte("abcdefghijklmnopqrstuvwxyz0123456789")
293 var separatorChars = []byte("._-")
294
295 func randomPath(length int64) string {
296 path := "/"
297 for int64(len(path)) < length {
298 chunkLength := rand.Int63n(length-int64(len(path))) + 1
299 chunk := randomFilename(chunkLength)
300 path += chunk
301 remaining := length - int64(len(path))
302 if remaining == 1 {
303 path += randomFilename(1)
304 } else if remaining > 1 {
305 path += "/"
306 }
307 }
308 return path
309 }
310
311 func randomFilename(length int64) string {
312 b := make([]byte, length)
313 wasSeparator := true
314 for i := range b {
315 if !wasSeparator && i < len(b)-1 && rand.Intn(4) == 0 {
316 b[i] = separatorChars[rand.Intn(len(separatorChars))]
317 wasSeparator = true
318 } else {
319 b[i] = filenameChars[rand.Intn(len(filenameChars))]
320 wasSeparator = false
321 }
322 }
323 return string(b)
324 }
325
View as plain text