@@ -18,17 +18,22 @@ package assets
1818
1919import (
2020 "fmt"
21+ "net/http"
22+ "net/http/httptest"
2123 "net/url"
2224 "os"
25+ "path"
2326 "path/filepath"
2427 "strings"
2528 "sync"
29+ "sync/atomic"
2630 "testing"
2731
2832 "k8s.io/kops/pkg/apis/kops"
2933 "k8s.io/kops/pkg/featureflag"
3034 "k8s.io/kops/pkg/testutils/golden"
3135 "k8s.io/kops/util/pkg/hashing"
36+ "k8s.io/kops/util/pkg/vfs"
3237)
3338
3439func buildAssetBuilder (t * testing.T ) * AssetBuilder {
@@ -339,3 +344,131 @@ func TestAssetBuilderConcurrentCollection(t *testing.T) {
339344 }
340345 }
341346}
347+
348+ func resetDownloadedFileHashes (t * testing.T ) {
349+ t .Helper ()
350+
351+ downloadedFileHashes .Clear ()
352+ t .Cleanup (downloadedFileHashes .Clear )
353+ }
354+
355+ func hashHandler (assetPath string , hash string , requests * atomic.Int64 ) http.HandlerFunc {
356+ return func (w http.ResponseWriter , r * http.Request ) {
357+ if r .URL .Path != assetPath + ".sha256" {
358+ // The VFS retries 404 and 5xx responses.
359+ http .Error (w , "not found" , http .StatusForbidden )
360+ return
361+ }
362+ requests .Add (1 )
363+ fmt .Fprintf (w , "%s %s\n " , hash , path .Base (assetPath ))
364+ }
365+ }
366+
367+ func newHashServer (t * testing.T , assetPath string , hash string , requests * atomic.Int64 ) * httptest.Server {
368+ t .Helper ()
369+
370+ server := httptest .NewServer (hashHandler (assetPath , hash , requests ))
371+ t .Cleanup (server .Close )
372+
373+ return server
374+ }
375+
376+ func TestFindHashCachesDownloadedHashesByResolvedURL (t * testing.T ) {
377+ resetDownloadedFileHashes (t )
378+
379+ const assetPath = "/binaries/example/linux/amd64/example"
380+ const canonicalHash = "2222222222222222222222222222222222222222222222222222222222222222"
381+ const mirroredHash = "3333333333333333333333333333333333333333333333333333333333333333"
382+
383+ var canonicalRequests atomic.Int64
384+ canonicalServer := newHashServer (t , assetPath , canonicalHash , & canonicalRequests )
385+
386+ var mirroredRequests atomic.Int64
387+ mirroredServer := newHashServer (t , assetPath , mirroredHash , & mirroredRequests )
388+
389+ assetURL , err := url .Parse (canonicalServer .URL + assetPath )
390+ if err != nil {
391+ t .Fatalf ("error parsing asset url: %v" , err )
392+ }
393+
394+ vfsContext := vfs .NewVFSContext ()
395+
396+ // Each builder registers the asset, but only the first downloads its checksum.
397+ for i := 0 ; i < 3 ; i ++ {
398+ builder := NewAssetBuilder (vfsContext , & kops.AssetsSpec {}, false )
399+
400+ asset , err := builder .RemapFile (assetURL , nil )
401+ if err != nil {
402+ t .Fatalf ("error remapping file with builder %d: %v" , i , err )
403+ }
404+ if actual := asset .SHAValue .Hex (); actual != canonicalHash {
405+ t .Errorf ("unexpected hash from builder %d: actual %q, expected %q" , i , actual , canonicalHash )
406+ }
407+ if actual := len (builder .FileAssets ()); actual != 1 {
408+ t .Errorf ("expected builder %d to register 1 file asset, got %d" , i , actual )
409+ }
410+ }
411+
412+ // The mirror must not reuse the canonical URL's cached hash.
413+ fileRepository := mirroredServer .URL
414+ mirroredBuilder := NewAssetBuilder (vfsContext , & kops.AssetsSpec {FileRepository : & fileRepository }, false )
415+ mirroredAsset , err := mirroredBuilder .RemapFile (assetURL , nil )
416+ if err != nil {
417+ t .Fatalf ("error remapping mirrored file: %v" , err )
418+ }
419+ if actual := mirroredAsset .SHAValue .Hex (); actual != mirroredHash {
420+ t .Errorf ("unexpected mirrored hash: actual %q, expected %q" , actual , mirroredHash )
421+ }
422+
423+ if actual := canonicalRequests .Load (); actual != 1 {
424+ t .Errorf ("expected 1 canonical checksum request, got %d" , actual )
425+ }
426+ if actual := mirroredRequests .Load (); actual != 1 {
427+ t .Errorf ("expected 1 mirrored checksum request, got %d" , actual )
428+ }
429+ }
430+
431+ func TestFindHashDoesNotCacheFailures (t * testing.T ) {
432+ resetDownloadedFileHashes (t )
433+
434+ const assetPath = "/binaries/example/linux/amd64/example"
435+ const hash = "4444444444444444444444444444444444444444444444444444444444444444"
436+
437+ var published atomic.Bool
438+ var requests atomic.Int64
439+ handler := hashHandler (assetPath , hash , & requests )
440+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
441+ if ! published .Load () {
442+ // The VFS retries 404 and 5xx responses.
443+ http .Error (w , "not found" , http .StatusForbidden )
444+ return
445+ }
446+ handler .ServeHTTP (w , r )
447+ }))
448+ t .Cleanup (server .Close )
449+
450+ assetURL , err := url .Parse (server .URL + assetPath )
451+ if err != nil {
452+ t .Fatalf ("error parsing asset url: %v" , err )
453+ }
454+
455+ vfsContext := vfs .NewVFSContext ()
456+ builder := NewAssetBuilder (vfsContext , & kops.AssetsSpec {}, false )
457+
458+ if _ , err := builder .RemapFile (assetURL , nil ); err == nil {
459+ t .Fatal ("expected an error while the checksum file is unavailable" )
460+ }
461+
462+ published .Store (true )
463+
464+ asset , err := builder .RemapFile (assetURL , nil )
465+ if err != nil {
466+ t .Fatalf ("error remapping file after the checksum file was published: %v" , err )
467+ }
468+ if actual := asset .SHAValue .Hex (); actual != hash {
469+ t .Errorf ("unexpected hash: actual %q, expected %q" , actual , hash )
470+ }
471+ if actual := requests .Load (); actual != 1 {
472+ t .Errorf ("expected 1 successful checksum request, got %d" , actual )
473+ }
474+ }
0 commit comments