Skip to content

Commit 5007164

Browse files
authored
support lazy-loading cache (#221)
* support just-in-time cache image pulls * add logging * remove unused * ch * fix artifact locaiton
1 parent d8c29b7 commit 5007164

File tree

5 files changed

+127
-60
lines changed

5 files changed

+127
-60
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
changelog:
2+
- type: NON_USER_FACING
3+
description: Expand the Wasm Cache to allow just-in-time pulling of images based on ref. Also adds additional logging to the cache.

tools/wasme/cli/builder/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ RUN npm install -g @bazel/bazelisk
1212

1313
# TODO: use the latest stable release package after the WASI supported is officially released
1414
# this corresponds to https://github.com/tinygo-org/tinygo/commit/9a015f4f6441fd68eabe35b3dc9748a5467d5934
15-
RUN wget https://19333-136505169-gh.circle-artifacts.com/0/tmp/tinygo_amd64.deb
15+
RUN wget https://storage.googleapis.com/getenvoy-package/tinygo-nightly-packages/tinygo_amd64.deb
1616
RUN dpkg -i tinygo_amd64.deb && rm tinygo_amd64.deb
1717

1818
RUN wget https://golang.org/dl/go1.15.2.linux-amd64.tar.gz

tools/wasme/pkg/cache/cache.go

+45-56
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package cache
22

33
import (
44
"context"
5+
"crypto"
6+
"encoding/hex"
57
"fmt"
8+
"github.com/solo-io/go-utils/contextutils"
9+
"go.uber.org/zap"
610
"io"
7-
"log"
811
"net/http"
912
"path"
1013
"strconv"
11-
"sync"
14+
"strings"
1215
"time"
1316

1417
"github.com/pkg/errors"
@@ -32,75 +35,33 @@ type Cache interface {
3235
type CacheImpl struct {
3336
Puller pull.ImagePuller
3437

38+
logger *zap.SugaredLogger
39+
3540
cacheState cacheState
3641
}
3742

3843
func NewCache(puller pull.ImagePuller) Cache {
39-
return &CacheImpl{
40-
Puller: puller,
41-
}
42-
}
43-
44-
type cacheState struct {
45-
images map[string]pull.Image
46-
imagesLock sync.RWMutex
47-
}
48-
49-
func (c *cacheState) add(image pull.Image) {
50-
desc, err := image.Descriptor()
51-
if err != nil {
52-
// image is missing descriptor, should never happen
53-
// TODO: better logging impl
54-
log.Printf("error: image %v missing code descriptor", image.Ref())
55-
return
56-
}
57-
if c.find(desc.Digest) != nil {
58-
// check existence for idempotence
59-
// technically metadata can be different, but it's fine for now.
60-
return
61-
}
62-
c.imagesLock.Lock()
63-
if c.images == nil {
64-
c.images = make(map[string]pull.Image)
65-
}
66-
c.images[image.Ref()] = image
67-
c.imagesLock.Unlock()
44+
return NewCacheWithConext(context.Background(), puller)
6845
}
6946

70-
func (c *cacheState) find(digest digest.Digest) pull.Image {
71-
c.imagesLock.RLock()
72-
defer c.imagesLock.RUnlock()
73-
if c.images == nil {
74-
return nil
75-
}
76-
for _, image := range c.images {
77-
desc, err := image.Descriptor()
78-
if err != nil {
79-
log.Printf("error: image %v missing code descriptor", image.Ref())
80-
return nil
81-
}
82-
83-
if desc.Digest == digest {
84-
return image
85-
}
47+
func NewCacheWithConext(ctx context.Context, puller pull.ImagePuller) Cache {
48+
return &CacheImpl{
49+
Puller: puller,
50+
logger: contextutils.LoggerFrom(ctx),
8651
}
87-
return nil
88-
}
89-
func (c *cacheState) findImage(image string) pull.Image {
90-
c.imagesLock.RLock()
91-
defer c.imagesLock.RUnlock()
92-
return c.images[image]
9352
}
9453

9554
func (c *CacheImpl) Add(ctx context.Context, ref string) (digest.Digest, error) {
9655
if img := c.cacheState.findImage(ref); img != nil {
56+
c.logger.Debugf("found cached image ref %v", ref)
9757
desc, err := img.Descriptor()
9858
if err != nil {
9959
return "", err
10060
}
10161
return desc.Digest, nil
10262
}
10363

64+
c.logger.Debugf("attempting to pull image %v", ref)
10465
image, err := c.Puller.Pull(ctx, ref)
10566
if err != nil {
10667
return "", err
@@ -113,6 +74,8 @@ func (c *CacheImpl) Add(ctx context.Context, ref string) (digest.Digest, error)
11374

11475
c.cacheState.add(image)
11576

77+
c.logger.Debugf("pulled image %v (digest: %v)", ref, desc.Digest)
78+
11679
return desc.Digest, nil
11780
}
11881

@@ -125,11 +88,33 @@ func (c *CacheImpl) Get(ctx context.Context, digest digest.Digest) (model.Filter
12588
}
12689

12790
func (c *CacheImpl) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
91+
// we support two paths:
92+
// /<HASH> - used in gloo
93+
// /image-name - used here to cache on demand
94+
_, file := path.Split(r.URL.Path)
95+
switch {
96+
case len(file) == hex.EncodedLen(crypto.SHA256.Size()):
97+
c.ServeHTTPSha(rw, r, file)
98+
default:
99+
// assume that the path is a ref. add it to cache
100+
ref := strings.TrimPrefix(r.URL.Path, "/")
101+
c.logger.Debugf("serving http request for image ref %v", ref)
102+
desc, err := c.Add(r.Context(), ref)
103+
if err != nil {
104+
c.logger.Errorf("failed to add or fetch descriptor %v: %v", ref, err)
105+
http.Error(rw, err.Error(), http.StatusBadRequest)
106+
return
107+
}
108+
c.ServeHTTPSha(rw, r, desc.Encoded())
109+
}
110+
}
111+
112+
func (c *CacheImpl) ServeHTTPSha(rw http.ResponseWriter, r *http.Request, sha string) {
128113
// parse the url
129114
ctx := r.Context()
130-
_, file := path.Split(r.URL.Path)
131-
image := c.cacheState.find(digest.Digest("sha256:" + file))
115+
image := c.cacheState.find(digest.Digest("sha256:" + sha))
132116
if image == nil {
117+
c.logger.Errorf("image with sha %v not found", sha)
133118
http.NotFound(rw, r)
134119
return
135120
}
@@ -142,6 +127,7 @@ func (c *CacheImpl) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
142127

143128
filter, err := image.FetchFilter(ctx)
144129
if err != nil {
130+
c.logger.Errorf("failed fetching image content")
145131
http.NotFound(rw, r)
146132
return
147133
}
@@ -151,12 +137,14 @@ func (c *CacheImpl) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
151137

152138
rw.Header().Set("Content-Type", desc.MediaType)
153139
rw.Header().Set("Etag", "\""+string(desc.Digest)+"\"")
140+
c.logger.Debugf("writing image content...")
154141
if rs, ok := filter.(io.ReadSeeker); ok {
155142
// content of digests never changes so set mod time to a constant
156143
// don't use zero time because serve content doesn't use that.
157144
modTime := time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
158-
http.ServeContent(rw, r, file, modTime, rs)
145+
http.ServeContent(rw, r, sha, modTime, rs)
159146
} else {
147+
c.logger.Debugf("writing image content")
160148
rw.Header().Add("Content-Length", strconv.Itoa(int(desc.Size)))
161149
if r.Method != "HEAD" {
162150
_, err = io.Copy(rw, filter)
@@ -166,4 +154,5 @@ func (c *CacheImpl) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
166154
}
167155
}
168156
}
157+
c.logger.Debugf("finished writing %v: %v bytes", image.Ref(), desc.Size)
169158
}

tools/wasme/pkg/cache/cache_state.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package cache
2+
3+
import (
4+
"github.com/opencontainers/go-digest"
5+
"github.com/sirupsen/logrus"
6+
"github.com/solo-io/wasm/tools/wasme/pkg/pull"
7+
"sync"
8+
)
9+
10+
type cacheState struct {
11+
images map[string]pull.Image
12+
imagesLock sync.RWMutex
13+
}
14+
15+
func (c *cacheState) add(image pull.Image) {
16+
desc, err := image.Descriptor()
17+
if err != nil {
18+
// image is missing descriptor, should never happen
19+
// TODO: better logging impl
20+
logrus.Errorf("error: image %v missing code descriptor", image.Ref())
21+
return
22+
}
23+
if c.find(desc.Digest) != nil {
24+
// check existence for idempotence
25+
// technically metadata can be different, but it's fine for now.
26+
return
27+
}
28+
c.imagesLock.Lock()
29+
if c.images == nil {
30+
c.images = make(map[string]pull.Image)
31+
}
32+
c.images[image.Ref()] = image
33+
logrus.Debugf("added image " + desc.Digest.String())
34+
c.imagesLock.Unlock()
35+
}
36+
37+
func (c *cacheState) find(digest digest.Digest) pull.Image {
38+
c.imagesLock.RLock()
39+
defer c.imagesLock.RUnlock()
40+
if c.images == nil {
41+
return nil
42+
}
43+
logrus.Debugf("searching for image " + digest.String())
44+
for _, image := range c.images {
45+
desc, err := image.Descriptor()
46+
if err != nil {
47+
logrus.Errorf("error: image %v missing code descriptor", image.Ref())
48+
return nil
49+
}
50+
51+
if desc.Digest == digest {
52+
return image
53+
}
54+
}
55+
return nil
56+
}
57+
58+
func (c *cacheState) findImage(image string) pull.Image {
59+
c.imagesLock.RLock()
60+
defer c.imagesLock.RUnlock()
61+
return c.images[image]
62+
}
63+
64+
func (c *cacheState) remove(image string) {
65+
c.imagesLock.Lock()
66+
defer c.imagesLock.Unlock()
67+
delete(c.images, image)
68+
}

tools/wasme/pkg/defaults/defaults.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package defaults
22

33
import (
4+
"context"
45
"github.com/solo-io/wasm/tools/wasme/pkg/cache"
56
"github.com/solo-io/wasm/tools/wasme/pkg/pull"
67
"github.com/solo-io/wasm/tools/wasme/pkg/resolver"
78
)
89

910
func NewDefaultCache() cache.Cache {
11+
return cache.NewCache(NewDefaultPuller())
12+
}
13+
14+
func NewDefaultCacheWithContext(ctx context.Context) cache.Cache {
15+
return cache.NewCacheWithConext(ctx, NewDefaultPuller())
16+
}
17+
18+
func NewDefaultPuller() pull.ImagePuller {
1019
// Can pull from non-private registries
1120
res, _ := resolver.NewResolver("", "", true, false)
12-
puller := pull.NewPuller(res)
13-
14-
return cache.NewCache(puller)
21+
return pull.NewPuller(res)
1522
}

0 commit comments

Comments
 (0)