Skip to content

Commit efb2d62

Browse files
authored
cache: lazily pull contents to populate cache (#951)
1 parent 6928f6d commit efb2d62

File tree

2 files changed

+83
-54
lines changed

2 files changed

+83
-54
lines changed

Diff for: pkg/v1/cache/cache.go

+56-34
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package cache
33

44
import (
55
"errors"
6+
"io"
67

78
"github.com/google/go-containerregistry/pkg/logs"
89
v1 "github.com/google/go-containerregistry/pkg/v1"
10+
"github.com/google/go-containerregistry/pkg/v1/types"
911
)
1012

1113
// Cache encapsulates methods to interact with cached layers.
@@ -55,46 +57,66 @@ func (i *image) Layers() ([]v1.Layer, error) {
5557

5658
var out []v1.Layer
5759
for _, l := range ls {
58-
// Check if this layer is present in the cache in compressed
59-
// form.
60-
digest, err := l.Digest()
61-
if err != nil {
62-
return nil, err
63-
}
64-
if cl, err := i.c.Get(digest); err == nil {
65-
// Layer found in the cache.
66-
logs.Progress.Printf("Layer %s found (compressed) in cache", digest)
67-
out = append(out, cl)
68-
continue
69-
} else if err != nil && err != ErrNotFound {
70-
return nil, err
71-
}
60+
out = append(out, &lazyLayer{inner: l, c: i.c})
61+
}
62+
return out, nil
63+
}
7264

73-
// Check if this layer is present in the cache in
74-
// uncompressed form.
75-
diffID, err := l.DiffID()
76-
if err != nil {
77-
return nil, err
78-
}
79-
if cl, err := i.c.Get(diffID); err == nil {
80-
// Layer found in the cache.
81-
logs.Progress.Printf("Layer %s found (uncompressed) in cache", diffID)
82-
out = append(out, cl)
83-
} else if err != nil && err != ErrNotFound {
84-
return nil, err
85-
}
65+
type lazyLayer struct {
66+
inner v1.Layer
67+
c Cache
68+
}
8669

87-
// Not cached, fall through to real layer.
88-
l, err = i.c.Put(l)
89-
if err != nil {
90-
return nil, err
91-
}
92-
out = append(out, l)
70+
func (l *lazyLayer) Compressed() (io.ReadCloser, error) {
71+
digest, err := l.inner.Digest()
72+
if err != nil {
73+
return nil, err
74+
}
9375

76+
if cl, err := l.c.Get(digest); err == nil {
77+
// Layer found in the cache.
78+
logs.Progress.Printf("Layer %s found (compressed) in cache", digest)
79+
return cl.Compressed()
80+
} else if err != nil && err != ErrNotFound {
81+
return nil, err
9482
}
95-
return out, nil
83+
84+
// Not cached, pull and return the real layer.
85+
logs.Progress.Printf("Layer %s not found (compressed) in cache, getting", digest)
86+
rl, err := l.c.Put(l.inner)
87+
if err != nil {
88+
return nil, err
89+
}
90+
return rl.Compressed()
9691
}
9792

93+
func (l *lazyLayer) Uncompressed() (io.ReadCloser, error) {
94+
diffID, err := l.inner.DiffID()
95+
if err != nil {
96+
return nil, err
97+
}
98+
if cl, err := l.c.Get(diffID); err == nil {
99+
// Layer found in the cache.
100+
logs.Progress.Printf("Layer %s found (uncompressed) in cache", diffID)
101+
return cl.Uncompressed()
102+
} else if err != nil && err != ErrNotFound {
103+
return nil, err
104+
}
105+
106+
// Not cached, pull and return the real layer.
107+
logs.Progress.Printf("Layer %s not found (uncompressed) in cache, getting", diffID)
108+
rl, err := l.c.Put(l.inner)
109+
if err != nil {
110+
return nil, err
111+
}
112+
return rl.Uncompressed()
113+
}
114+
115+
func (l *lazyLayer) Size() (int64, error) { return l.inner.Size() }
116+
func (l *lazyLayer) DiffID() (v1.Hash, error) { return l.inner.DiffID() }
117+
func (l *lazyLayer) Digest() (v1.Hash, error) { return l.inner.Digest() }
118+
func (l *lazyLayer) MediaType() (types.MediaType, error) { return l.inner.MediaType() }
119+
98120
func (i *image) LayerByDigest(h v1.Hash) (v1.Layer, error) {
99121
l, err := i.c.Get(h)
100122
if err == ErrNotFound {

Diff for: pkg/v1/cache/cache_test.go

+27-20
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,58 @@ package cache
22

33
import (
44
"errors"
5+
"io"
6+
"io/ioutil"
57
"testing"
68

79
v1 "github.com/google/go-containerregistry/pkg/v1"
810
"github.com/google/go-containerregistry/pkg/v1/random"
911
"github.com/google/go-containerregistry/pkg/v1/validate"
1012
)
1113

12-
// TestCache tests that the cache is populated when LayerByDigest is called.
13-
func TestCache(t *testing.T) {
14-
numLayers := 5
15-
img, err := random.Image(10, int64(numLayers))
14+
func TestImage(t *testing.T) {
15+
img, err := random.Image(1024, 5)
1616
if err != nil {
1717
t.Fatalf("random.Image: %v", err)
1818
}
1919
m := &memcache{map[v1.Hash]v1.Layer{}}
2020
img = Image(img, m)
2121

22-
// Cache is empty.
23-
if len(m.m) != 0 {
24-
t.Errorf("Before consuming, cache is non-empty: %+v", m.m)
25-
}
26-
27-
// Consume each layer, cache gets populated.
28-
if _, err := img.Layers(); err != nil {
29-
t.Fatalf("Layers: %v", err)
22+
// Validate twice to hit the cache.
23+
if err := validate.Image(img); err != nil {
24+
t.Errorf("Validate: %v", err)
3025
}
31-
if got, want := len(m.m), numLayers; got != want {
32-
t.Errorf("Cache has %d entries, want %d", got, want)
26+
if err := validate.Image(img); err != nil {
27+
t.Errorf("Validate: %v", err)
3328
}
3429
}
3530

36-
func TestImage(t *testing.T) {
31+
func TestLayersLazy(t *testing.T) {
3732
img, err := random.Image(1024, 5)
3833
if err != nil {
3934
t.Fatalf("random.Image: %v", err)
4035
}
4136
m := &memcache{map[v1.Hash]v1.Layer{}}
4237
img = Image(img, m)
4338

44-
// Validate twice to hit the cache.
45-
if err := validate.Image(img); err != nil {
46-
t.Errorf("Validate: %v", err)
39+
layers, err := img.Layers()
40+
if err != nil {
41+
t.Fatalf("img.Layers: %v", err)
4742
}
48-
if err := validate.Image(img); err != nil {
49-
t.Errorf("Validate: %v", err)
43+
44+
// After calling Layers, nothing is cached.
45+
if got, want := len(m.m), 0; got != want {
46+
t.Errorf("Cache has %d entries, want %d", got, want)
47+
}
48+
49+
rc, err := layers[0].Uncompressed()
50+
if err != nil {
51+
t.Fatalf("layer.Uncompressed: %v", err)
52+
}
53+
io.Copy(ioutil.Discard, rc)
54+
55+
if got, expected := len(m.m), 1; got != expected {
56+
t.Errorf("expected %v layers in cache after reading, got %v", expected, got)
5057
}
5158
}
5259

0 commit comments

Comments
 (0)