diff options
author | feistel <6742251-feistel@users.noreply.gitlab.com> | 2021-07-26 18:30:26 +0300 |
---|---|---|
committer | feistel <6742251-feistel@users.noreply.gitlab.com> | 2021-11-08 18:24:14 +0300 |
commit | 77a733b8af393f81c1143ca27324384b1801a090 (patch) | |
tree | c4e17cbfedb872806b3b3c73959806a79d3e3a93 /internal/vfs | |
parent | 81e6fdba4fa75b3e882cde818c25a2a76f531de7 (diff) |
refactor: read and use file_sha256 as cache key for zip VFS
Now that the Rails API is serving the archive file_sha256 in the
lookup response, we can move away from using the base URL as the
key to cache the archive.
Changelog: change
Diffstat (limited to 'internal/vfs')
-rw-r--r-- | internal/vfs/local/root_test.go | 10 | ||||
-rw-r--r-- | internal/vfs/local/vfs.go | 2 | ||||
-rw-r--r-- | internal/vfs/local/vfs_test.go | 2 | ||||
-rw-r--r-- | internal/vfs/vfs.go | 6 | ||||
-rw-r--r-- | internal/vfs/zip/archive_test.go | 9 | ||||
-rw-r--r-- | internal/vfs/zip/vfs.go | 17 | ||||
-rw-r--r-- | internal/vfs/zip/vfs_test.go | 34 |
7 files changed, 51 insertions, 29 deletions
diff --git a/internal/vfs/local/root_test.go b/internal/vfs/local/root_test.go index a835bae3..e7bbb63f 100644 --- a/internal/vfs/local/root_test.go +++ b/internal/vfs/local/root_test.go @@ -15,7 +15,7 @@ import ( func TestValidatePath(t *testing.T) { ctx := context.Background() - rootVFS, err := localVFS.Root(ctx, ".") + rootVFS, err := localVFS.Root(ctx, ".", "") require.NoError(t, err) root := rootVFS.(*Root) @@ -64,7 +64,7 @@ func TestValidatePath(t *testing.T) { func TestReadlink(t *testing.T) { ctx := context.Background() - root, err := localVFS.Root(ctx, ".") + root, err := localVFS.Root(ctx, ".", "") require.NoError(t, err) tests := map[string]struct { @@ -140,7 +140,7 @@ func TestReadlinkAbsolutePath(t *testing.T) { err = os.Symlink(dirFilePath, symlinkPath) require.NoError(t, err) - root, err := localVFS.Root(context.Background(), dirPath) + root, err := localVFS.Root(context.Background(), dirPath, "") require.NoError(t, err) tests := map[string]struct { @@ -169,7 +169,7 @@ func TestReadlinkAbsolutePath(t *testing.T) { func TestLstat(t *testing.T) { ctx := context.Background() - root, err := localVFS.Root(ctx, ".") + root, err := localVFS.Root(ctx, ".", "") require.NoError(t, err) tests := map[string]struct { @@ -233,7 +233,7 @@ func TestLstat(t *testing.T) { func TestOpen(t *testing.T) { ctx := context.Background() - root, err := localVFS.Root(ctx, ".") + root, err := localVFS.Root(ctx, ".", "") require.NoError(t, err) tests := map[string]struct { diff --git a/internal/vfs/local/vfs.go b/internal/vfs/local/vfs.go index 5cca94fd..cb48ad1c 100644 --- a/internal/vfs/local/vfs.go +++ b/internal/vfs/local/vfs.go @@ -15,7 +15,7 @@ var errNotDirectory = errors.New("path needs to be a directory") type VFS struct{} -func (localFs VFS) Root(ctx context.Context, path string) (vfs.Root, error) { +func (localFs VFS) Root(ctx context.Context, path string, cacheKey string) (vfs.Root, error) { rootPath, err := filepath.Abs(path) if err != nil { return nil, err diff --git a/internal/vfs/local/vfs_test.go b/internal/vfs/local/vfs_test.go index 2072d110..3f84bb39 100644 --- a/internal/vfs/local/vfs_test.go +++ b/internal/vfs/local/vfs_test.go @@ -96,7 +96,7 @@ func TestVFSRoot(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - rootVFS, err := localVFS.Root(context.Background(), filepath.Join(tmpDir, test.path)) + rootVFS, err := localVFS.Root(context.Background(), filepath.Join(tmpDir, test.path), "") if test.expectedIsNotExist { require.Equal(t, test.expectedIsNotExist, vfs.IsNotExist(err)) diff --git a/internal/vfs/vfs.go b/internal/vfs/vfs.go index 40fe8bc7..a93f9e26 100644 --- a/internal/vfs/vfs.go +++ b/internal/vfs/vfs.go @@ -13,7 +13,7 @@ import ( // VFS abstracts the things Pages needs to serve a static site from disk. type VFS interface { - Root(ctx context.Context, path string) (Root, error) + Root(ctx context.Context, path string, cacheKey string) (Root, error) Name() string Reconfigure(config *config.Config) error } @@ -34,8 +34,8 @@ func (i *instrumentedVFS) log(ctx context.Context) *logrus.Entry { return log.ContextLogger(ctx).WithField("vfs", i.fs.Name()) } -func (i *instrumentedVFS) Root(ctx context.Context, path string) (Root, error) { - root, err := i.fs.Root(ctx, path) +func (i *instrumentedVFS) Root(ctx context.Context, path string, cacheKey string) (Root, error) { + root, err := i.fs.Root(ctx, path, cacheKey) i.increment("Root", err) i.log(ctx). diff --git a/internal/vfs/zip/archive_test.go b/internal/vfs/zip/archive_test.go index acd89d49..423d9eaf 100644 --- a/internal/vfs/zip/archive_test.go +++ b/internal/vfs/zip/archive_test.go @@ -97,6 +97,7 @@ func TestOpenCached(t *testing.T) { tests := []struct { name string vfsPath string + sha256 string filePath string expectedArchiveStatus archiveStatus expectedOpenErr error @@ -106,6 +107,7 @@ func TestOpenCached(t *testing.T) { { name: "open file first time", vfsPath: testServerURL + "/public.zip", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", filePath: "index.html", // we expect five requests to: // read resource and zip metadata @@ -116,6 +118,7 @@ func TestOpenCached(t *testing.T) { { name: "open file second time", vfsPath: testServerURL + "/public.zip", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", filePath: "index.html", // we expect one request to read file with cached data offset expectedRequests: 1, @@ -124,6 +127,7 @@ func TestOpenCached(t *testing.T) { { name: "when the URL changes", vfsPath: testServerURL + "/public.zip?new-secret", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", filePath: "index.html", expectedRequests: 1, expectedArchiveStatus: archiveOpened, @@ -131,6 +135,7 @@ func TestOpenCached(t *testing.T) { { name: "when opening cached file and content changes", vfsPath: testServerURL + "/public.zip?changed-content=1", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", filePath: "index.html", expectedRequests: 1, // we receive an error on `read` as `open` offset is already cached @@ -140,6 +145,7 @@ func TestOpenCached(t *testing.T) { { name: "after content change archive is reloaded", vfsPath: testServerURL + "/public.zip?new-secret", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", filePath: "index.html", expectedRequests: 5, expectedArchiveStatus: archiveOpened, @@ -147,6 +153,7 @@ func TestOpenCached(t *testing.T) { { name: "when opening non-cached file and content changes", vfsPath: testServerURL + "/public.zip?changed-content=1", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", filePath: "subdir/hello.html", expectedRequests: 1, // we receive an error on `read` as `open` offset is already cached @@ -158,7 +165,7 @@ func TestOpenCached(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { start := atomic.LoadInt64(&requests) - zip, err := fs.Root(context.Background(), test.vfsPath) + zip, err := fs.Root(context.Background(), test.vfsPath, test.sha256) require.NoError(t, err) f, err := zip.Open(context.Background(), test.filePath) diff --git a/internal/vfs/zip/vfs.go b/internal/vfs/zip/vfs.go index c90d7614..40fa617b 100644 --- a/internal/vfs/zip/vfs.go +++ b/internal/vfs/zip/vfs.go @@ -164,15 +164,20 @@ func (fs *zipVFS) keyFromPath(path string) (string, error) { // If findOrOpenArchive returns errAlreadyCached, the for loop will continue // to try and find the cached archive or return if there's an error, for example // if the context is canceled. -func (fs *zipVFS) Root(ctx context.Context, path string) (vfs.Root, error) { - key, err := fs.keyFromPath(path) - if err != nil { - return nil, err +func (fs *zipVFS) Root(ctx context.Context, path string, cacheKey string) (vfs.Root, error) { + // TODO: update acceptance tests mocked response to return a cacheKey + if cacheKey == "" { + k, err := fs.keyFromPath(path) + if err != nil { + return nil, err + } + + cacheKey = k } // we do it in loop to not use any additional locks for { - root, err := fs.findOrOpenArchive(ctx, key, path) + root, err := fs.findOrOpenArchive(ctx, cacheKey, path) if err == errAlreadyCached { continue } @@ -252,7 +257,7 @@ func (fs *zipVFS) findOrCreateArchive(ctx context.Context, key string) (*zipArch } // findOrOpenArchive gets archive from cache and tries to open it -func (fs *zipVFS) findOrOpenArchive(ctx context.Context, key, path string) (*zipArchive, error) { +func (fs *zipVFS) findOrOpenArchive(ctx context.Context, key string, path string) (*zipArchive, error) { zipArchive, err := fs.findOrCreateArchive(ctx, key) if err != nil { return nil, err diff --git a/internal/vfs/zip/vfs_test.go b/internal/vfs/zip/vfs_test.go index fb368a69..9343405c 100644 --- a/internal/vfs/zip/vfs_test.go +++ b/internal/vfs/zip/vfs_test.go @@ -23,17 +23,21 @@ func TestVFSRoot(t *testing.T) { tests := map[string]struct { path string + sha256 string expectedErrMsg string }{ "zip_file_exists": { - path: "/public.zip", + path: "/public.zip", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", }, "zip_file_does_not_exist": { path: "/unknown", + sha256: "filedoesnotexist", expectedErrMsg: vfs.ErrNotExist{Inner: httprange.ErrNotFound}.Error(), }, "invalid_url": { path: "/%", + sha256: "invalidurl", expectedErrMsg: "invalid URL", }, } @@ -42,7 +46,7 @@ func TestVFSRoot(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { - root, err := vfs.Root(context.Background(), url+tt.path) + root, err := vfs.Root(context.Background(), url+tt.path, tt.sha256) if tt.expectedErrMsg != "" { require.Error(t, err) require.Contains(t, err.Error(), tt.expectedErrMsg) @@ -77,7 +81,8 @@ func TestVFSFindOrOpenArchiveConcurrentAccess(t *testing.T) { path := testServerURL + "/public.zip" vfs := New(&zipCfg).(*zipVFS) - root, err := vfs.Root(context.Background(), path) + key := "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75" + root, err := vfs.Root(context.Background(), path, key) require.NoError(t, err) done := make(chan struct{}) @@ -93,13 +98,13 @@ func TestVFSFindOrOpenArchiveConcurrentAccess(t *testing.T) { default: vfs.cache.Flush() - vfs.cache.SetDefault(path, root) + vfs.cache.SetDefault(key, root) } } }() require.Eventually(t, func() bool { - _, err := vfs.findOrOpenArchive(context.Background(), path, path) + _, err := vfs.findOrOpenArchive(context.Background(), key, path) return err == errAlreadyCached }, 3*time.Second, time.Nanosecond) } @@ -113,6 +118,7 @@ func TestVFSFindOrOpenArchiveRefresh(t *testing.T) { tests := map[string]struct { path string + sha256 string expirationInterval time.Duration refreshInterval time.Duration @@ -122,6 +128,7 @@ func TestVFSFindOrOpenArchiveRefresh(t *testing.T) { }{ "after cache expiry of successful open a new archive is returned": { path: "/public.zip", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", expirationInterval: expiryInterval, expectNewArchive: true, expectOpenError: false, @@ -134,6 +141,7 @@ func TestVFSFindOrOpenArchiveRefresh(t *testing.T) { }, "subsequent open during refresh interval does refresh archive": { path: "/public.zip", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", expirationInterval: time.Second, refreshInterval: time.Second, // refresh always expectNewArchive: false, @@ -142,6 +150,7 @@ func TestVFSFindOrOpenArchiveRefresh(t *testing.T) { }, "subsequent open before refresh interval does not refresh archive": { path: "/public.zip", + sha256: "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75", expirationInterval: time.Second, refreshInterval: time.Millisecond, // very short interval should not refresh expectNewArchive: false, @@ -170,7 +179,7 @@ func TestVFSFindOrOpenArchiveRefresh(t *testing.T) { path := testServerURL + test.path // create a new archive and increase counters - archive1, err1 := vfs.findOrOpenArchive(context.Background(), path, path) + archive1, err1 := vfs.findOrOpenArchive(context.Background(), test.sha256, path) if test.expectOpenError { require.Error(t, err1) require.Nil(t, archive1) @@ -178,7 +187,7 @@ func TestVFSFindOrOpenArchiveRefresh(t *testing.T) { require.NoError(t, err1) } - item1, exp1, found := vfs.cache.GetWithExpiration(path) + item1, exp1, found := vfs.cache.GetWithExpiration(test.sha256) require.True(t, found) // give some time to for timeouts to fire @@ -186,7 +195,7 @@ func TestVFSFindOrOpenArchiveRefresh(t *testing.T) { if test.expectNewArchive { // should return a new archive - archive2, err2 := vfs.findOrOpenArchive(context.Background(), path, path) + archive2, err2 := vfs.findOrOpenArchive(context.Background(), test.sha256, path) if test.expectOpenError { require.Error(t, err2) require.Nil(t, archive2) @@ -198,11 +207,11 @@ func TestVFSFindOrOpenArchiveRefresh(t *testing.T) { } // should return exactly the same archive - archive2, err2 := vfs.findOrOpenArchive(context.Background(), path, path) + archive2, err2 := vfs.findOrOpenArchive(context.Background(), test.sha256, path) require.Equal(t, archive1, archive2, "same archive is returned") require.Equal(t, err1, err2, "same error for the same archive") - item2, exp2, found := vfs.cache.GetWithExpiration(path) + item2, exp2, found := vfs.cache.GetWithExpiration(test.sha256) require.True(t, found) require.Equal(t, item1, item2, "same item is returned") @@ -224,9 +233,10 @@ func TestVFSReconfigureTransport(t *testing.T) { fileURL := testhelpers.ToFileProtocol(t, "group/zip.gitlab.io/public.zip") vfs := New(&zipCfg) + key := "d6b318b399cfe9a1c8483e49847ee49a2676d8cfd6df57ec64d971ad03640a75" // try to open a file URL without registering the file protocol - _, err := vfs.Root(context.Background(), fileURL) + _, err := vfs.Root(context.Background(), fileURL, key) require.Error(t, err) require.Contains(t, err.Error(), "unsupported protocol scheme \"file\"") @@ -237,7 +247,7 @@ func TestVFSReconfigureTransport(t *testing.T) { err = vfs.Reconfigure(&config.Config{Zip: cfg}) require.NoError(t, err) - root, err := vfs.Root(context.Background(), fileURL) + root, err := vfs.Root(context.Background(), fileURL, key) require.NoError(t, err) fi, err := root.Lstat(context.Background(), "index.html") |