33
"github.com/google/go-cmp/cmp"
34
"github.com/google/go-cmp/cmp/cmpopts"
35
"github.com/google/go-containerregistry/pkg/crane"
36
"github.com/google/go-containerregistry/pkg/registry"
37
"github.com/google/go-containerregistry/pkg/v1/empty"
38
"github.com/google/go-containerregistry/pkg/v1/mutate"
39
"github.com/google/go-containerregistry/pkg/v1/remote"
40
"github.com/google/go-containerregistry/pkg/v1/types"
42
extensions "istio.io/api/extensions/v1alpha1"
43
"istio.io/istio/pkg/util/sets"
47
var wasmHeader = append(wasmMagicNumber, []byte{0x1, 0x00, 0x00, 0x00}...)
49
func TestWasmCache(t *testing.T) {
51
tsNumRequest := int32(0)
53
httpData := append(wasmHeader, []byte("data")...)
54
invalidHTTPData := []byte("invalid binary")
55
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56
atomic.AddInt32(&tsNumRequest, 1)
58
if r.URL.Path == "/different-url" {
59
w.Write(append(httpData, []byte("different data")...))
60
} else if r.URL.Path == "/invalid-wasm-header" {
61
w.Write(invalidHTTPData)
67
httpDataSha := sha256.Sum256(httpData)
68
httpDataCheckSum := hex.EncodeToString(httpDataSha[:])
69
invalidHTTPDataSha := sha256.Sum256(invalidHTTPData)
70
invalidHTTPDataCheckSum := hex.EncodeToString(invalidHTTPDataSha[:])
74
tos := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
75
atomic.AddInt32(&tsNumRequest, 1)
79
ou, err := url.Parse(tos.URL)
84
dockerImageDigest, invalidOCIImageDigest := setupOCIRegistry(t, ou.Host)
86
ociWasmFile := fmt.Sprintf("%s.wasm", dockerImageDigest)
87
ociURLWithTag := fmt.Sprintf("oci://%s/test/valid/docker:v0.1.0", ou.Host)
88
ociURLWithLatestTag := fmt.Sprintf("oci://%s/test/valid/docker:latest", ou.Host)
89
ociURLWithDigest := fmt.Sprintf("oci://%s/test/valid/docker@sha256:%s", ou.Host, dockerImageDigest)
92
cacheHitSha := sha256.Sum256([]byte("cachehit"))
93
cacheHitSum := hex.EncodeToString(cacheHitSha[:])
97
initialCachedModules map[moduleKey]cacheEntry
98
initialCachedChecksums map[string]*checksumEntry
100
purgeInterval time.Duration
101
wasmModuleExpiry time.Duration
102
checkPurgeTimeout time.Duration
103
getOptions GetOptions
104
wantCachedModules map[moduleKey]*cacheEntry
105
wantCachedChecksums map[string]*checksumEntry
107
wantErrorMsgPrefix string
112
initialCachedModules: map[moduleKey]cacheEntry{},
113
initialCachedChecksums: map[string]*checksumEntry{},
115
getOptions: GetOptions{
116
Checksum: httpDataCheckSum,
117
ResourceName: "namespace.resource",
118
ResourceVersion: "0",
119
RequestTimeout: time.Second * 10,
121
wantCachedModules: map[moduleKey]*cacheEntry{
122
{name: ts.URL, checksum: httpDataCheckSum}: {modulePath: httpDataCheckSum + ".wasm"},
124
wantCachedChecksums: map[string]*checksumEntry{
125
ts.URL: {checksum: httpDataCheckSum, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
127
wantFileName: fmt.Sprintf("%s.wasm", httpDataCheckSum),
128
wantVisitServer: true,
132
initialCachedModules: map[moduleKey]cacheEntry{
133
{name: moduleNameFromURL(ts.URL), checksum: cacheHitSum}: {modulePath: "test.wasm"},
135
initialCachedChecksums: map[string]*checksumEntry{},
137
getOptions: GetOptions{
138
Checksum: cacheHitSum,
139
ResourceName: "namespace.resource",
140
ResourceVersion: "0",
141
RequestTimeout: time.Second * 10,
143
wantCachedModules: map[moduleKey]*cacheEntry{
144
{name: ts.URL, checksum: cacheHitSum}: {modulePath: "test.wasm"},
146
wantCachedChecksums: map[string]*checksumEntry{
147
ts.URL: {checksum: cacheHitSum, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
149
wantFileName: "test.wasm",
150
wantVisitServer: false,
153
name: "invalid scheme",
154
initialCachedModules: map[moduleKey]cacheEntry{},
155
initialCachedChecksums: map[string]*checksumEntry{},
156
fetchURL: "foo://abc",
157
getOptions: GetOptions{
158
Checksum: httpDataCheckSum,
159
RequestTimeout: time.Second * 10,
161
wantCachedModules: map[moduleKey]*cacheEntry{},
162
wantCachedChecksums: map[string]*checksumEntry{},
163
wantFileName: fmt.Sprintf("%s.wasm", httpDataCheckSum),
164
wantErrorMsgPrefix: "unsupported Wasm module downloading URL scheme: foo",
165
wantVisitServer: false,
168
name: "download failure",
169
initialCachedModules: map[moduleKey]cacheEntry{},
170
initialCachedChecksums: map[string]*checksumEntry{},
171
fetchURL: "https://-invalid-url",
172
getOptions: GetOptions{
173
RequestTimeout: time.Second * 10,
175
wantCachedModules: map[moduleKey]*cacheEntry{},
176
wantCachedChecksums: map[string]*checksumEntry{},
177
wantErrorMsgPrefix: "wasm module download failed after 5 attempts, last error: Get \"https://-invalid-url\"",
178
wantVisitServer: false,
181
name: "wrong checksum",
182
initialCachedModules: map[moduleKey]cacheEntry{},
183
initialCachedChecksums: map[string]*checksumEntry{},
185
getOptions: GetOptions{
186
Checksum: "wrongchecksum\n",
187
RequestTimeout: time.Second * 10,
189
wantCachedModules: map[moduleKey]*cacheEntry{},
190
wantCachedChecksums: map[string]*checksumEntry{},
191
wantErrorMsgPrefix: fmt.Sprintf("module downloaded from %v has checksum %s, which does not match", ts.URL, httpDataCheckSum),
192
wantVisitServer: true,
197
name: "different url same checksum",
198
initialCachedModules: map[moduleKey]cacheEntry{
199
{name: moduleNameFromURL(ts.URL), checksum: httpDataCheckSum}: {modulePath: fmt.Sprintf("%s.wasm", httpDataCheckSum)},
201
initialCachedChecksums: map[string]*checksumEntry{},
202
fetchURL: ts.URL + "/different-url",
203
getOptions: GetOptions{
204
Checksum: httpDataCheckSum,
205
RequestTimeout: time.Second * 10,
207
wantCachedModules: map[moduleKey]*cacheEntry{
208
{name: ts.URL, checksum: httpDataCheckSum}: {modulePath: httpDataCheckSum + ".wasm"},
210
wantCachedChecksums: map[string]*checksumEntry{},
211
wantErrorMsgPrefix: fmt.Sprintf("module downloaded from %v/different-url has checksum", ts.URL),
212
wantVisitServer: true,
215
name: "invalid wasm header",
216
initialCachedModules: map[moduleKey]cacheEntry{
217
{name: moduleNameFromURL(ts.URL), checksum: httpDataCheckSum}: {modulePath: fmt.Sprintf("%s.wasm", httpDataCheckSum)},
219
initialCachedChecksums: map[string]*checksumEntry{},
220
fetchURL: ts.URL + "/invalid-wasm-header",
221
getOptions: GetOptions{
222
Checksum: invalidHTTPDataCheckSum,
223
RequestTimeout: time.Second * 10,
225
wantCachedModules: map[moduleKey]*cacheEntry{
226
{name: ts.URL, checksum: httpDataCheckSum}: {modulePath: httpDataCheckSum + ".wasm"},
228
wantCachedChecksums: map[string]*checksumEntry{},
229
wantErrorMsgPrefix: fmt.Sprintf("fetched Wasm binary from %s is invalid", ts.URL+"/invalid-wasm-header"),
230
wantVisitServer: true,
233
name: "purge on expiry",
234
initialCachedModules: map[moduleKey]cacheEntry{},
235
initialCachedChecksums: map[string]*checksumEntry{},
237
purgeInterval: 1 * time.Millisecond,
238
wasmModuleExpiry: 1 * time.Millisecond,
239
checkPurgeTimeout: 5 * time.Second,
240
getOptions: GetOptions{
241
Checksum: httpDataCheckSum,
242
RequestTimeout: time.Second * 10,
244
wantCachedModules: map[moduleKey]*cacheEntry{},
245
wantCachedChecksums: map[string]*checksumEntry{},
246
wantFileName: fmt.Sprintf("%s.wasm", httpDataCheckSum),
247
wantVisitServer: true,
250
name: "fetch oci without digest",
251
initialCachedModules: map[moduleKey]cacheEntry{},
252
initialCachedChecksums: map[string]*checksumEntry{},
253
fetchURL: ociURLWithTag,
254
getOptions: GetOptions{
255
ResourceName: "namespace.resource",
256
ResourceVersion: "0",
257
RequestTimeout: time.Second * 10,
259
wantCachedModules: map[moduleKey]*cacheEntry{
260
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
262
wantCachedChecksums: map[string]*checksumEntry{
263
ociURLWithTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
265
wantFileName: ociWasmFile,
266
wantVisitServer: true,
269
name: "fetch oci with digest",
270
initialCachedModules: map[moduleKey]cacheEntry{},
271
initialCachedChecksums: map[string]*checksumEntry{},
272
fetchURL: ociURLWithTag,
273
getOptions: GetOptions{
274
Checksum: dockerImageDigest,
275
ResourceName: "namespace.resource",
276
ResourceVersion: "0",
277
RequestTimeout: time.Second * 10,
279
wantCachedModules: map[moduleKey]*cacheEntry{
280
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
282
wantCachedChecksums: map[string]*checksumEntry{
283
ociURLWithTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
285
wantFileName: ociWasmFile,
286
wantVisitServer: true,
289
name: "cache hit for tagged oci url with digest",
290
initialCachedModules: map[moduleKey]cacheEntry{
291
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
293
initialCachedChecksums: map[string]*checksumEntry{},
294
fetchURL: ociURLWithTag,
295
getOptions: GetOptions{
296
Checksum: dockerImageDigest,
297
ResourceName: "namespace.resource",
298
ResourceVersion: "0",
299
RequestTimeout: time.Second * 10,
301
wantCachedModules: map[moduleKey]*cacheEntry{
302
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
304
wantCachedChecksums: map[string]*checksumEntry{
305
ociURLWithTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
307
wantFileName: ociWasmFile,
308
wantVisitServer: false,
311
name: "cache hit for tagged oci url without digest",
312
initialCachedModules: map[moduleKey]cacheEntry{
313
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
315
initialCachedChecksums: map[string]*checksumEntry{
317
checksum: dockerImageDigest,
318
resourceVersionByResource: map[string]string{
319
"namespace.resource": "123456",
323
fetchURL: ociURLWithTag,
324
getOptions: GetOptions{
325
ResourceName: "namespace.resource",
326
ResourceVersion: "0",
327
RequestTimeout: time.Second * 10,
329
wantCachedModules: map[moduleKey]*cacheEntry{
330
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
332
wantCachedChecksums: map[string]*checksumEntry{
333
ociURLWithTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
335
wantFileName: ociWasmFile,
336
wantVisitServer: false,
339
name: "cache miss for tagged oci url without digest",
340
initialCachedModules: map[moduleKey]cacheEntry{
341
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
343
initialCachedChecksums: map[string]*checksumEntry{},
344
getOptions: GetOptions{
345
ResourceName: "namespace.resource",
346
ResourceVersion: "0",
347
RequestTimeout: time.Second * 10,
349
fetchURL: ociURLWithTag,
350
wantCachedModules: map[moduleKey]*cacheEntry{
351
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
353
wantCachedChecksums: map[string]*checksumEntry{
354
ociURLWithTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
356
wantFileName: ociWasmFile,
357
wantVisitServer: true,
360
name: "cache hit for oci url suffixed by digest",
361
initialCachedModules: map[moduleKey]cacheEntry{
362
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
364
initialCachedChecksums: map[string]*checksumEntry{},
365
fetchURL: ociURLWithDigest,
366
getOptions: GetOptions{
367
ResourceName: "namespace.resource",
368
ResourceVersion: "0",
369
RequestTimeout: time.Second * 10,
371
wantCachedModules: map[moduleKey]*cacheEntry{
372
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
374
wantCachedChecksums: map[string]*checksumEntry{},
375
wantFileName: ociWasmFile,
376
wantVisitServer: false,
379
name: "pull due to pull-always policy when cache hit",
380
initialCachedModules: map[moduleKey]cacheEntry{
381
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
383
initialCachedChecksums: map[string]*checksumEntry{
385
checksum: dockerImageDigest,
386
resourceVersionByResource: map[string]string{
387
"namespace.resource": "123456",
391
fetchURL: ociURLWithTag,
392
getOptions: GetOptions{
393
ResourceName: "namespace.resource",
394
ResourceVersion: "0",
395
RequestTimeout: time.Second * 10,
396
PullPolicy: extensions.PullPolicy_Always,
398
wantCachedModules: map[moduleKey]*cacheEntry{
399
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
401
wantCachedChecksums: map[string]*checksumEntry{
402
ociURLWithTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
404
wantFileName: ociWasmFile,
405
wantVisitServer: true,
408
name: "do not pull due to resourceVersion is the same",
409
initialCachedModules: map[moduleKey]cacheEntry{
410
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
412
initialCachedChecksums: map[string]*checksumEntry{
414
checksum: dockerImageDigest,
415
resourceVersionByResource: map[string]string{
416
"namespace.resource": "123456",
420
fetchURL: ociURLWithTag,
421
getOptions: GetOptions{
422
ResourceName: "namespace.resource",
423
ResourceVersion: "123456",
424
RequestTimeout: time.Second * 10,
425
PullPolicy: extensions.PullPolicy_Always,
427
wantCachedModules: map[moduleKey]*cacheEntry{
428
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
430
wantCachedChecksums: map[string]*checksumEntry{
431
ociURLWithTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "123456"}},
433
wantFileName: ociWasmFile,
434
wantVisitServer: false,
437
name: "pull due to if-not-present policy when cache hit",
438
initialCachedModules: map[moduleKey]cacheEntry{
439
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
441
initialCachedChecksums: map[string]*checksumEntry{
443
checksum: dockerImageDigest,
444
resourceVersionByResource: map[string]string{
445
"namespace.resource": "123456",
449
fetchURL: ociURLWithTag,
450
getOptions: GetOptions{
451
ResourceName: "namespace.resource",
452
ResourceVersion: "0",
453
RequestTimeout: time.Second * 10,
454
PullPolicy: extensions.PullPolicy_IfNotPresent,
456
wantCachedModules: map[moduleKey]*cacheEntry{
457
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
459
wantCachedChecksums: map[string]*checksumEntry{
460
ociURLWithTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
462
wantFileName: ociWasmFile,
463
wantVisitServer: false,
466
name: "do not pull in spite of pull-always policy due to checksum",
467
initialCachedModules: map[moduleKey]cacheEntry{
468
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
470
fetchURL: ociURLWithTag,
471
getOptions: GetOptions{
472
Checksum: dockerImageDigest,
473
ResourceName: "namespace.resource",
474
ResourceVersion: "0",
475
RequestTimeout: time.Second * 10,
476
PullPolicy: extensions.PullPolicy_Always,
478
wantCachedModules: map[moduleKey]*cacheEntry{
479
{name: moduleNameFromURL(ociURLWithTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
481
wantCachedChecksums: map[string]*checksumEntry{
482
ociURLWithTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
484
wantFileName: ociWasmFile,
485
wantVisitServer: false,
488
name: "pull due to latest tag",
489
initialCachedModules: map[moduleKey]cacheEntry{
490
{name: moduleNameFromURL(ociURLWithLatestTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
492
initialCachedChecksums: map[string]*checksumEntry{
493
ociURLWithLatestTag: {
494
checksum: dockerImageDigest,
495
resourceVersionByResource: map[string]string{
496
"namespace.resource": "123456",
500
fetchURL: ociURLWithLatestTag,
501
getOptions: GetOptions{
502
ResourceName: "namespace.resource",
503
ResourceVersion: "0",
504
RequestTimeout: time.Second * 10,
505
PullPolicy: extensions.PullPolicy_UNSPECIFIED_POLICY,
507
wantCachedModules: map[moduleKey]*cacheEntry{
508
{name: moduleNameFromURL(ociURLWithLatestTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
510
wantCachedChecksums: map[string]*checksumEntry{
511
ociURLWithLatestTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
513
wantFileName: ociWasmFile,
514
wantVisitServer: true,
517
name: "do not pull in spite of latest tag due to checksum",
518
initialCachedModules: map[moduleKey]cacheEntry{
519
{name: moduleNameFromURL(ociURLWithLatestTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
521
initialCachedChecksums: map[string]*checksumEntry{
522
ociURLWithLatestTag: {
523
checksum: dockerImageDigest,
524
resourceVersionByResource: map[string]string{
525
"namespace.resource": "123456",
529
fetchURL: ociURLWithLatestTag,
530
getOptions: GetOptions{
531
ResourceName: "namespace.resource",
532
ResourceVersion: "0",
533
RequestTimeout: time.Second * 10,
534
Checksum: dockerImageDigest,
535
PullPolicy: extensions.PullPolicy_UNSPECIFIED_POLICY,
537
wantCachedModules: map[moduleKey]*cacheEntry{
538
{name: moduleNameFromURL(ociURLWithLatestTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
540
wantCachedChecksums: map[string]*checksumEntry{
541
ociURLWithLatestTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
543
wantFileName: ociWasmFile,
544
wantVisitServer: false,
547
name: "do not pull in spite of latest tag due to IfNotPresent policy",
548
initialCachedModules: map[moduleKey]cacheEntry{
549
{name: moduleNameFromURL(ociURLWithLatestTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
551
initialCachedChecksums: map[string]*checksumEntry{
552
ociURLWithLatestTag: {
553
checksum: dockerImageDigest,
554
resourceVersionByResource: map[string]string{
555
"namespace.resource": "123456",
559
fetchURL: ociURLWithLatestTag,
560
getOptions: GetOptions{
561
ResourceName: "namespace.resource",
562
ResourceVersion: "0",
563
RequestTimeout: time.Second * 10,
564
PullPolicy: extensions.PullPolicy_IfNotPresent,
566
wantCachedModules: map[moduleKey]*cacheEntry{
567
{name: moduleNameFromURL(ociURLWithLatestTag), checksum: dockerImageDigest}: {modulePath: ociWasmFile},
569
wantCachedChecksums: map[string]*checksumEntry{
570
ociURLWithLatestTag: {checksum: dockerImageDigest, resourceVersionByResource: map[string]string{"namespace.resource": "0"}},
572
wantFileName: ociWasmFile,
573
wantVisitServer: false,
576
name: "purge OCI image on expiry",
577
initialCachedModules: map[moduleKey]cacheEntry{
578
{name: moduleNameFromURL(ociURLWithTag) + "-purged", checksum: dockerImageDigest}: {
579
modulePath: ociWasmFile,
580
referencingURLs: sets.New(ociURLWithTag),
583
initialCachedChecksums: map[string]*checksumEntry{
585
checksum: dockerImageDigest,
586
resourceVersionByResource: map[string]string{
587
"namespace.resource": "123456",
591
checksum: "test-checksum",
592
resourceVersionByResource: map[string]string{
593
"namespace.resource2": "123456",
597
fetchURL: ociURLWithDigest,
598
purgeInterval: 1 * time.Millisecond,
599
wasmModuleExpiry: 1 * time.Millisecond,
600
getOptions: GetOptions{
601
ResourceName: "namespace.resource",
602
ResourceVersion: "0",
603
RequestTimeout: time.Second * 10,
605
checkPurgeTimeout: 5 * time.Second,
606
wantCachedModules: map[moduleKey]*cacheEntry{},
607
wantCachedChecksums: map[string]*checksumEntry{
608
"test-url": {checksum: "test-checksum", resourceVersionByResource: map[string]string{"namespace.resource2": "123456"}},
610
wantFileName: ociWasmFile,
611
wantVisitServer: true,
614
name: "fetch oci timed out",
615
initialCachedModules: map[moduleKey]cacheEntry{},
616
fetchURL: ociURLWithTag,
617
getOptions: GetOptions{
618
ResourceName: "namespace.resource",
619
ResourceVersion: "0",
622
wantCachedModules: map[moduleKey]*cacheEntry{},
623
wantCachedChecksums: map[string]*checksumEntry{},
624
wantErrorMsgPrefix: fmt.Sprintf("could not fetch Wasm OCI image: could not fetch manifest: Get \"https://%s/v2/\"", ou.Host),
625
wantVisitServer: false,
628
name: "fetch oci with wrong digest",
629
initialCachedModules: map[moduleKey]cacheEntry{},
630
fetchURL: ociURLWithTag,
631
getOptions: GetOptions{
632
ResourceName: "namespace.resource",
633
ResourceVersion: "0",
634
RequestTimeout: time.Second * 10,
635
Checksum: "wrongdigest",
637
wantCachedModules: map[moduleKey]*cacheEntry{},
638
wantCachedChecksums: map[string]*checksumEntry{},
639
wantErrorMsgPrefix: fmt.Sprintf(
640
"module downloaded from %v has checksum %v, which does not match:", fmt.Sprintf("oci://%s/test/valid/docker:v0.1.0", ou.Host), dockerImageDigest,
642
wantVisitServer: true,
645
name: "fetch invalid oci",
646
initialCachedModules: map[moduleKey]cacheEntry{},
647
fetchURL: fmt.Sprintf("oci://%s/test/invalid", ou.Host),
648
getOptions: GetOptions{
649
ResourceName: "namespace.resource",
650
ResourceVersion: "0",
651
Checksum: invalidOCIImageDigest,
652
RequestTimeout: time.Second * 10,
654
wantCachedModules: map[moduleKey]*cacheEntry{},
655
wantCachedChecksums: map[string]*checksumEntry{},
656
wantErrorMsgPrefix: `could not fetch Wasm binary: the given image is in invalid format as an OCI image: 2 errors occurred:
657
* could not parse as compat variant: invalid media type application/vnd.oci.image.layer.v1.tar (expect application/vnd.oci.image.layer.v1.tar+gzip)
658
* could not parse as oci variant: number of layers must be 2 but got 1`,
659
wantVisitServer: true,
663
for _, c := range cases {
664
t.Run(c.name, func(t *testing.T) {
665
tmpDir := t.TempDir()
666
options := defaultOptions()
667
if c.purgeInterval != 0 {
668
options.PurgeInterval = c.purgeInterval
670
if c.wasmModuleExpiry != 0 {
671
options.ModuleExpiry = c.wasmModuleExpiry
673
cache := NewLocalFileCache(tmpDir, options)
674
cache.httpFetcher.initialBackoff = time.Microsecond
675
defer close(cache.stopChan)
677
var cacheHitKey *moduleKey
678
initTime := time.Now()
680
for k, m := range c.initialCachedModules {
681
filePath := generateModulePath(t, tmpDir, k.name, m.modulePath)
682
err := os.WriteFile(filePath, []byte("data/\n"), 0o644)
684
t.Fatalf("failed to write initial wasm module file %v", err)
686
mkey := moduleKey{name: k.name, checksum: k.checksum}
688
cache.modules[mkey] = &cacheEntry{modulePath: filePath, last: initTime}
689
if m.referencingURLs != nil {
690
cache.modules[mkey].referencingURLs = m.referencingURLs.Copy()
692
cache.modules[mkey].referencingURLs = sets.New[string]()
695
if moduleNameFromURL(c.fetchURL) == k.name && c.getOptions.Checksum == k.checksum {
700
for k, m := range c.initialCachedChecksums {
701
cache.checksums[k] = m
705
for k, m := range c.wantCachedModules {
706
c.wantCachedModules[k].modulePath = generateModulePath(t, tmpDir, k.name, m.modulePath)
710
atomic.StoreInt32(&tsNumRequest, 0)
711
if c.getOptions.PullSecret == nil {
712
c.getOptions.PullSecret = []byte{}
714
gotFilePath, gotErr := cache.Get(c.fetchURL, c.getOptions)
715
serverVisited := atomic.LoadInt32(&tsNumRequest) > 0
717
if c.checkPurgeTimeout > 0 {
718
moduleDeleted := false
719
for start := time.Now(); time.Since(start) < c.checkPurgeTimeout; {
721
err = filepath.Walk(tmpDir,
722
func(path string, info os.FileInfo, err error) error {
732
if err == nil && fileCount == 0 {
739
t.Fatalf("Wasm modules are not purged before purge timeout")
744
if cacheHitKey != nil {
745
if entry, ok := cache.modules[*cacheHitKey]; ok && entry.last == initTime {
746
t.Errorf("Wasm module cache entry's last access time not updated after get operation, key: %v", *cacheHitKey)
750
if diff := cmp.Diff(c.wantCachedModules, cache.modules,
751
cmpopts.IgnoreFields(cacheEntry{}, "last", "referencingURLs"),
752
cmp.AllowUnexported(cacheEntry{}),
754
t.Errorf("unexpected module cache: (-want, +got)\n%v", diff)
757
if diff := cmp.Diff(c.wantCachedChecksums, cache.checksums,
758
cmp.AllowUnexported(checksumEntry{}),
760
t.Errorf("unexpected checksums: (-want, +got)\n%v", diff)
765
wantFilePath := generateModulePath(t, tmpDir, moduleNameFromURL(c.fetchURL), c.wantFileName)
766
if c.wantErrorMsgPrefix != "" {
768
t.Errorf("Wasm module cache lookup got no error, want error prefix `%v`", c.wantErrorMsgPrefix)
769
} else if !strings.Contains(gotErr.Error(), c.wantErrorMsgPrefix) {
770
t.Errorf("Wasm module cache lookup got error `%v`, want error prefix `%v`", gotErr, c.wantErrorMsgPrefix)
772
} else if gotFilePath != wantFilePath {
773
t.Errorf("Wasm module local file path got %v, want %v", gotFilePath, wantFilePath)
775
t.Errorf("got unexpected error %v", gotErr)
778
if c.wantVisitServer != serverVisited {
779
t.Errorf("test wasm binary server encountered the unexpected visiting status got %v, want %v", serverVisited, c.wantVisitServer)
785
func setupOCIRegistry(t *testing.T, host string) (dockerImageDigest, invalidOCIImageDigest string) {
787
ref := fmt.Sprintf("%s/test/valid/docker:v0.1.0", host)
788
binary := append(wasmHeader, []byte("this is wasm plugin")...)
789
transport := remote.DefaultTransport.(*http.Transport).Clone()
790
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
791
fetchOpt := crane.WithTransport(transport)
794
l, err := newMockLayer(types.DockerLayer,
795
map[string][]byte{"plugin.wasm": binary})
799
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
805
manifest, err := img.Manifest()
809
manifest.MediaType = types.DockerManifestSchema2
812
err = crane.Push(img, ref, fetchOpt)
818
ref = fmt.Sprintf("%s/test/valid/docker:latest", host)
819
err = crane.Push(img, ref, fetchOpt)
826
dockerImageDigest = d.Hex
829
ref = fmt.Sprintf("%s/test/invalid", host)
830
l, err = newMockLayer(types.OCIUncompressedLayer, map[string][]byte{"not-wasm.txt": []byte("a")})
834
img2, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
840
img2 = mutate.MediaType(img2, types.OCIManifestSchema1)
843
invalidOCIImageDigest = d.Hex
846
err = crane.Push(img2, ref, fetchOpt)
853
func TestWasmCachePolicyChangesUsingHTTP(t *testing.T) {
854
tmpDir := t.TempDir()
855
cache := NewLocalFileCache(tmpDir, defaultOptions())
856
defer close(cache.stopChan)
859
binary1 := append(wasmHeader, 1)
860
binary2 := append(wasmHeader, 2)
863
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
864
if gotNumRequest <= 1 {
873
url2 := ts.URL + "/next"
874
wantFilePath1 := generateModulePath(t, tmpDir, url1, fmt.Sprintf("%x.wasm", sha256.Sum256(binary1)))
875
wantFilePath2 := generateModulePath(t, tmpDir, url2, fmt.Sprintf("%x.wasm", sha256.Sum256(binary2)))
876
var defaultPullPolicy extensions.PullPolicy
878
testWasmGet := func(downloadURL string, policy extensions.PullPolicy, resourceVersion string, wantFilePath string, wantNumRequest int) {
880
gotFilePath, err := cache.Get(downloadURL, GetOptions{
881
ResourceName: "namespace.resource",
882
ResourceVersion: resourceVersion,
883
RequestTimeout: time.Second * 10,
884
PullSecret: []byte{},
888
t.Fatalf("failed to download Wasm module: %v", err)
890
if gotFilePath != wantFilePath {
891
t.Fatalf("wasm download path got %v want %v", gotFilePath, wantFilePath)
893
if gotNumRequest != wantNumRequest {
894
t.Fatalf("wasm download call got %v want %v", gotNumRequest, wantNumRequest)
899
testWasmGet(url1, defaultPullPolicy, "1", wantFilePath1, 1)
901
testWasmGet(url1, defaultPullPolicy, "2", wantFilePath1, 1)
903
testWasmGet(url1, extensions.PullPolicy_IfNotPresent, "3", wantFilePath1, 1)
905
testWasmGet(url1, extensions.PullPolicy_Always, "3", wantFilePath1, 1)
907
testWasmGet(url1, extensions.PullPolicy_Always, "4", wantFilePath1, 2)
909
testWasmGet(url2, extensions.PullPolicy_Always, "4", wantFilePath2, 3)
912
func TestAllInsecureServer(t *testing.T) {
913
tmpDir := t.TempDir()
914
options := defaultOptions()
915
options.InsecureRegistries = sets.New("*")
916
cache := NewLocalFileCache(tmpDir, options)
917
defer close(cache.stopChan)
921
tos := httptest.NewTLSServer(registry.New())
923
ou, err := url.Parse(tos.URL)
928
dockerImageDigest, _ := setupOCIRegistry(t, ou.Host)
929
ociURLWithTag := fmt.Sprintf("oci://%s/test/valid/docker:v0.1.0", ou.Host)
930
var defaultPullPolicy extensions.PullPolicy
932
gotFilePath, err := cache.Get(ociURLWithTag, GetOptions{
933
ResourceName: "namespace.resource",
934
ResourceVersion: "123456",
935
RequestTimeout: time.Second * 10,
936
PullSecret: []byte{},
937
PullPolicy: defaultPullPolicy,
940
t.Fatalf("failed to download Wasm module: %v", err)
943
wantFilePath := generateModulePath(t, tmpDir, moduleNameFromURL(ociURLWithTag), fmt.Sprintf("%s.wasm", dockerImageDigest))
944
if gotFilePath != wantFilePath {
945
t.Errorf("Wasm module local file path got %v, want %v", gotFilePath, wantFilePath)
949
func generateModulePath(t *testing.T, baseDir, resourceName, filename string) string {
951
sha := sha256.Sum256([]byte(resourceName))
952
moduleDir := filepath.Join(baseDir, hex.EncodeToString(sha[:]))
953
if _, err := os.Stat(moduleDir); errors.Is(err, os.ErrNotExist) {
954
err := os.Mkdir(moduleDir, 0o755)
956
t.Fatalf("failed to create module dir %s: %v", moduleDir, err)
959
return filepath.Join(moduleDir, filename)