1 package storage
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "reflect"
8 "strconv"
9 "testing"
10
11 "github.com/distribution/reference"
12 "github.com/docker/distribution"
13 "github.com/docker/distribution/testutil"
14 "github.com/opencontainers/go-digest"
15 )
16
17 func TestLinkedBlobStoreCreateWithMountFrom(t *testing.T) {
18 fooRepoName, _ := reference.WithName("nm/foo")
19 fooEnv := newManifestStoreTestEnv(t, fooRepoName, "thetag")
20 ctx := context.Background()
21 stats, err := mockRegistry(t, fooEnv.registry)
22 if err != nil {
23 t.Fatal(err)
24 }
25
26
27
28 testLayers := map[digest.Digest]io.ReadSeeker{}
29 for i := 0; i < 2; i++ {
30 rs, dgst, err := testutil.CreateRandomTarFile()
31 if err != nil {
32 t.Fatalf("unexpected error generating test layer file")
33 }
34
35 testLayers[dgst] = rs
36 }
37
38
39 for dgst, rs := range testLayers {
40 wr, err := fooEnv.repository.Blobs(fooEnv.ctx).Create(fooEnv.ctx)
41 if err != nil {
42 t.Fatalf("unexpected error creating test upload: %v", err)
43 }
44
45 if _, err := io.Copy(wr, rs); err != nil {
46 t.Fatalf("unexpected error copying to upload: %v", err)
47 }
48
49 if _, err := wr.Commit(fooEnv.ctx, distribution.Descriptor{Digest: dgst}); err != nil {
50 t.Fatalf("unexpected error finishing upload: %v", err)
51 }
52 }
53
54
55 barRepoName, _ := reference.WithName("nm/bar")
56 barRepo, err := fooEnv.registry.Repository(ctx, barRepoName)
57 if err != nil {
58 t.Fatalf("unexpected error getting repo: %v", err)
59 }
60
61
62 for dgst := range testLayers {
63 fooCanonical, _ := reference.WithDigest(fooRepoName, dgst)
64 option := WithMountFrom(fooCanonical)
65
66 createOpts := distribution.CreateOptions{}
67 if err := option.Apply(&createOpts); err != nil {
68 t.Fatalf("failed to apply MountFrom option: %v", err)
69 }
70 if !createOpts.Mount.ShouldMount || createOpts.Mount.From.String() != fooCanonical.String() {
71 t.Fatalf("unexpected create options: %#+v", createOpts.Mount)
72 }
73
74 _, err := barRepo.Blobs(ctx).Create(ctx, WithMountFrom(fooCanonical))
75 if err == nil {
76 t.Fatalf("unexpected non-error while mounting from %q: %v", fooRepoName.String(), err)
77 }
78 if _, ok := err.(distribution.ErrBlobMounted); !ok {
79 t.Fatalf("expected ErrMountFrom error, not %T: %v", err, err)
80 }
81 }
82 for dgst := range testLayers {
83 fooCanonical, _ := reference.WithDigest(fooRepoName, dgst)
84 count, exists := stats[fooCanonical.String()]
85 if !exists {
86 t.Errorf("expected entry %q not found among handled stat calls", fooCanonical.String())
87 } else if count != 1 {
88 t.Errorf("expected exactly one stat call for entry %q, not %d", fooCanonical.String(), count)
89 }
90 }
91
92 clearStats(stats)
93
94
95 bazRepoName, _ := reference.WithName("nm/baz")
96 bazRepo, err := fooEnv.registry.Repository(ctx, bazRepoName)
97 if err != nil {
98 t.Fatalf("unexpected error getting repo: %v", err)
99 }
100
101
102 for dgst := range testLayers {
103 fooCanonical, _ := reference.WithDigest(fooRepoName, dgst)
104 size, err := strconv.ParseInt("0x"+dgst.Hex()[:8], 0, 64)
105 if err != nil {
106 t.Fatal(err)
107 }
108 prepolutatedDescriptor := distribution.Descriptor{
109 Digest: dgst,
110 Size: size,
111 MediaType: "application/octet-stream",
112 }
113 _, err = bazRepo.Blobs(ctx).Create(ctx, WithMountFrom(fooCanonical), &statCrossMountCreateOption{
114 desc: prepolutatedDescriptor,
115 })
116 blobMounted, ok := err.(distribution.ErrBlobMounted)
117 if !ok {
118 t.Errorf("expected ErrMountFrom error, not %T: %v", err, err)
119 continue
120 }
121 if !reflect.DeepEqual(blobMounted.Descriptor, prepolutatedDescriptor) {
122 t.Errorf("unexpected descriptor: %#+v != %#+v", blobMounted.Descriptor, prepolutatedDescriptor)
123 }
124 }
125
126 if len(stats) != 0 {
127 t.Errorf("unexpected number of stats made: %d != %d", len(stats), len(testLayers))
128 }
129 }
130
131 func clearStats(stats map[string]int) {
132 for k := range stats {
133 delete(stats, k)
134 }
135 }
136
137
138
139
140
141 func mockRegistry(t *testing.T, nm distribution.Namespace) (map[string]int, error) {
142 registry, ok := nm.(*registry)
143 if !ok {
144 return nil, fmt.Errorf("not an expected type of registry: %T", nm)
145 }
146 stats := make(map[string]int)
147
148 registry.blobDescriptorServiceFactory = &mockBlobDescriptorServiceFactory{
149 t: t,
150 stats: stats,
151 }
152
153 return stats, nil
154 }
155
156 type mockBlobDescriptorServiceFactory struct {
157 t *testing.T
158 stats map[string]int
159 }
160
161 func (f *mockBlobDescriptorServiceFactory) BlobAccessController(svc distribution.BlobDescriptorService) distribution.BlobDescriptorService {
162 return &mockBlobDescriptorService{
163 BlobDescriptorService: svc,
164 t: f.t,
165 stats: f.stats,
166 }
167 }
168
169 type mockBlobDescriptorService struct {
170 distribution.BlobDescriptorService
171 t *testing.T
172 stats map[string]int
173 }
174
175 var _ distribution.BlobDescriptorService = &mockBlobDescriptorService{}
176
177 func (bs *mockBlobDescriptorService) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
178 statter, ok := bs.BlobDescriptorService.(*linkedBlobStatter)
179 if !ok {
180 return distribution.Descriptor{}, fmt.Errorf("unexpected blob descriptor service: %T", bs.BlobDescriptorService)
181 }
182
183 name := statter.repository.Named()
184 canonical, err := reference.WithDigest(name, dgst)
185 if err != nil {
186 return distribution.Descriptor{}, fmt.Errorf("failed to make canonical reference: %v", err)
187 }
188
189 bs.stats[canonical.String()]++
190 bs.t.Logf("calling Stat on %s", canonical.String())
191
192 return bs.BlobDescriptorService.Stat(ctx, dgst)
193 }
194
195
196 type statCrossMountCreateOption struct {
197 desc distribution.Descriptor
198 }
199
200 var _ distribution.BlobCreateOption = statCrossMountCreateOption{}
201
202 func (f statCrossMountCreateOption) Apply(v interface{}) error {
203 opts, ok := v.(*distribution.CreateOptions)
204 if !ok {
205 return fmt.Errorf("Unexpected create options: %#v", v)
206 }
207
208 if !opts.Mount.ShouldMount {
209 return nil
210 }
211
212 opts.Mount.Stat = &f.desc
213
214 return nil
215 }
216
View as plain text