Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Shushlin <vshushlin@gitlab.com>2020-09-25 15:38:18 +0300
committerVladimir Shushlin <vshushlin@gitlab.com>2020-09-25 15:38:18 +0300
commit8bc20438cb8f84bec72247ebcb1b75d8024f8c0c (patch)
tree47a5c1f40859ca5bcb5187c8524dec485c2cf486
parent7802bb75e8edafe05855fcbdb72aeea7bb906ae7 (diff)
parenteecf8c867d276470c6db93f26878a03b56ef4689 (diff)
Merge branch '443-implement-vfs-interface' into 'master'
VFS implementation for zip serving See merge request gitlab-org/gitlab-pages!351
-rw-r--r--acceptance_test.go89
-rw-r--r--helpers_test.go28
-rw-r--r--internal/serving/disk/local/serving.go2
-rw-r--r--internal/serving/disk/reader.go6
-rw-r--r--internal/serving/disk/symlink/path_test.go2
-rw-r--r--internal/serving/disk/zip/serving.go16
-rw-r--r--internal/serving/disk/zip/serving_test.go59
-rw-r--r--internal/source/gitlab/factory.go7
-rw-r--r--internal/testhelpers/tmpdir.go2
-rw-r--r--internal/vfs/local/vfs.go4
-rw-r--r--internal/vfs/vfs.go18
-rw-r--r--internal/vfs/zip/archive.go8
-rw-r--r--internal/vfs/zip/vfs.go90
-rw-r--r--internal/vfs/zip/vfs_test.go97
-rw-r--r--metrics/metrics.go6
-rw-r--r--shared/lookups/zip.gitlab.io.json16
16 files changed, 427 insertions, 23 deletions
diff --git a/acceptance_test.go b/acceptance_test.go
index f8c2a546..02c71f1d 100644
--- a/acceptance_test.go
+++ b/acceptance_test.go
@@ -21,6 +21,10 @@ import (
var pagesBinary = flag.String("gitlab-pages-binary", "./gitlab-pages", "Path to the gitlab-pages binary")
+const (
+ objectStorageMockServer = "127.0.0.1:37003"
+)
+
// TODO: Use TCP port 0 everywhere to avoid conflicts. The binary could output
// the actual port (and type of listener) for us to read in place of the
// hardcoded values below.
@@ -448,11 +452,14 @@ func TestHttpsOnlyDomainDisabled(t *testing.T) {
func TestPrometheusMetricsCanBeScraped(t *testing.T) {
skipUnlessEnabled(t)
+ _, cleanup := newZipFileServerURL(t, "shared/pages/group/zip.gitlab.io/public.zip")
+ defer cleanup()
+
teardown := RunPagesProcessWithStubGitLabServer(t, true, *pagesBinary, listeners, ":42345", []string{})
defer teardown()
// need to call an actual resource to populate certain metrics e.g. gitlab_pages_domains_source_api_requests_total
- res, err := GetPageFromListener(t, httpListener, "new-source-test.gitlab.io", "/my/pages/project/")
+ res, err := GetPageFromListener(t, httpListener, "zip.gitlab.io", "/index.html/")
require.NoError(t, err)
require.Equal(t, http.StatusOK, res.StatusCode)
@@ -480,9 +487,8 @@ func TestPrometheusMetricsCanBeScraped(t *testing.T) {
require.Contains(t, string(body), "gitlab_pages_serving_time_seconds_sum")
require.Contains(t, string(body), `gitlab_pages_domains_source_api_requests_total{status_code="200"}`)
require.Contains(t, string(body), `gitlab_pages_domains_source_api_call_duration{status_code="200"}`)
- // TODO: add test when Zip is enabled https://gitlab.com/gitlab-org/gitlab-pages/-/issues/443
- // require.Contains(t, string(body), `gitlab_pages_httprange_zip_reader_requests_total{status_code="200"}`)
- // require.Contains(t, string(body), `gitlab_pages_httprange_zip_reader_requests_duration{status_code="200"}`)
+ require.Contains(t, string(body), `gitlab_pages_object_storage_backend_requests_total{status_code="206"}`)
+ require.Contains(t, string(body), `gitlab_pages_object_storage_backend_requests_duration{status_code="206"}`)
}
func TestDisabledRedirects(t *testing.T) {
@@ -1925,3 +1931,78 @@ func TestDomainsSource(t *testing.T) {
})
}
}
+
+func TestZipServing(t *testing.T) {
+ skipUnlessEnabled(t)
+
+ var apiCalled bool
+ source := NewGitlabDomainsSourceStub(t, &apiCalled)
+ defer source.Close()
+
+ gitLabAPISecretKey := CreateGitLabAPISecretKeyFixtureFile(t)
+
+ pagesArgs := []string{"-gitlab-server", source.URL, "-api-secret-key", gitLabAPISecretKey, "-domain-config-source", "gitlab"}
+ teardown := RunPagesProcessWithEnvs(t, true, *pagesBinary, listeners, "", []string{}, pagesArgs...)
+ defer teardown()
+
+ _, cleanup := newZipFileServerURL(t, "shared/pages/group/zip.gitlab.io/public.zip")
+ defer cleanup()
+
+ tests := map[string]struct {
+ urlSuffix string
+ expectedStatusCode int
+ expectedContent string
+ }{
+ "base_domain_no_suffix": {
+ urlSuffix: "/",
+ expectedStatusCode: http.StatusOK,
+ expectedContent: "zip.gitlab.io/project/index.html\n",
+ },
+ "file_exists": {
+ urlSuffix: "/index.html",
+ expectedStatusCode: http.StatusOK,
+ expectedContent: "zip.gitlab.io/project/index.html\n",
+ },
+ "file_exists_in_subdir": {
+ urlSuffix: "/subdir/hello.html",
+ expectedStatusCode: http.StatusOK,
+ expectedContent: "zip.gitlab.io/project/subdir/hello.html\n",
+ },
+ "file_exists_symlink": {
+ urlSuffix: "/symlink.html",
+ expectedStatusCode: http.StatusOK,
+ expectedContent: "symlink.html->subdir/linked.html\n",
+ },
+ "dir": {
+ urlSuffix: "/subdir/",
+ expectedStatusCode: http.StatusNotFound,
+ expectedContent: "zip.gitlab.io/project/404.html\n",
+ },
+ "file_does_not_exist": {
+ urlSuffix: "/unknown.html",
+ expectedStatusCode: http.StatusNotFound,
+ expectedContent: "zip.gitlab.io/project/404.html\n",
+ },
+ "bad_symlink": {
+ urlSuffix: "/bad-symlink.html",
+ expectedStatusCode: http.StatusNotFound,
+ expectedContent: "zip.gitlab.io/project/404.html\n",
+ },
+ }
+
+ for name, tt := range tests {
+ t.Run(name, func(t *testing.T) {
+ response, err := GetPageFromListener(t, httpListener, "zip.gitlab.io", tt.urlSuffix)
+ require.NoError(t, err)
+ defer response.Body.Close()
+
+ require.Equal(t, tt.expectedStatusCode, response.StatusCode)
+ if tt.expectedStatusCode == http.StatusOK || tt.expectedStatusCode == http.StatusNotFound {
+ body, err := ioutil.ReadAll(response.Body)
+ require.NoError(t, err)
+
+ require.Equal(t, tt.expectedContent, string(body), "content mismatch")
+ }
+ })
+ }
+}
diff --git a/helpers_test.go b/helpers_test.go
index d08869fd..6ee11b86 100644
--- a/helpers_test.go
+++ b/helpers_test.go
@@ -522,3 +522,31 @@ func copyFile(dest, src string) error {
_, err = io.Copy(destFile, srcFile)
return err
}
+
+func newZipFileServerURL(t *testing.T, zipFilePath string) (string, func()) {
+ t.Helper()
+
+ m := http.NewServeMux()
+ m.HandleFunc("/public.zip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, zipFilePath)
+ }))
+
+ // create a listener with the desired port.
+ l, err := net.Listen("tcp", objectStorageMockServer)
+ require.NoError(t, err)
+
+ testServer := httptest.NewUnstartedServer(m)
+
+ // NewUnstartedServer creates a listener. Close that listener and replace
+ // with the one we created.
+ testServer.Listener.Close()
+ testServer.Listener = l
+
+ // Start the server.
+ testServer.Start()
+
+ return testServer.URL, func() {
+ // Cleanup.
+ testServer.Close()
+ }
+}
diff --git a/internal/serving/disk/local/serving.go b/internal/serving/disk/local/serving.go
index 230a71da..3b23b8e9 100644
--- a/internal/serving/disk/local/serving.go
+++ b/internal/serving/disk/local/serving.go
@@ -7,7 +7,7 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/vfs/local"
)
-var instance = disk.New(vfs.Instrumented(&local.VFS{}, "local"))
+var instance = disk.New(vfs.Instrumented(&local.VFS{}))
// Instance returns a serving instance that is capable of reading files
// from the disk
diff --git a/internal/serving/disk/reader.go b/internal/serving/disk/reader.go
index 5b34c556..559d332c 100644
--- a/internal/serving/disk/reader.go
+++ b/internal/serving/disk/reader.go
@@ -20,7 +20,7 @@ import (
// Reader is a disk access driver
type Reader struct {
- fileSizeMetric prometheus.Histogram
+ fileSizeMetric *prometheus.HistogramVec
vfs vfs.VFS
}
@@ -196,7 +196,7 @@ func (reader *Reader) serveFile(ctx context.Context, w http.ResponseWriter, r *h
w.Header().Set("Content-Type", contentType)
- reader.fileSizeMetric.Observe(float64(fi.Size()))
+ reader.fileSizeMetric.WithLabelValues(reader.vfs.Name()).Observe(float64(fi.Size()))
// Support vfs.SeekableFile if available (uncompressed files)
if rs, ok := file.(vfs.SeekableFile); ok {
@@ -231,7 +231,7 @@ func (reader *Reader) serveCustomFile(ctx context.Context, w http.ResponseWriter
return err
}
- reader.fileSizeMetric.Observe(float64(fi.Size()))
+ reader.fileSizeMetric.WithLabelValues(reader.vfs.Name()).Observe(float64(fi.Size()))
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Length", strconv.FormatInt(fi.Size(), 10))
diff --git a/internal/serving/disk/symlink/path_test.go b/internal/serving/disk/symlink/path_test.go
index 8c1d19b3..ef9726c1 100644
--- a/internal/serving/disk/symlink/path_test.go
+++ b/internal/serving/disk/symlink/path_test.go
@@ -21,7 +21,7 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/vfs/local"
)
-var fs = vfs.Instrumented(&local.VFS{}, "local")
+var fs = vfs.Instrumented(&local.VFS{})
type EvalSymlinksTest struct {
// If dest is empty, the path is created; otherwise the dest is symlinked to the path.
diff --git a/internal/serving/disk/zip/serving.go b/internal/serving/disk/zip/serving.go
new file mode 100644
index 00000000..95894fc9
--- /dev/null
+++ b/internal/serving/disk/zip/serving.go
@@ -0,0 +1,16 @@
+package zip
+
+import (
+ "gitlab.com/gitlab-org/gitlab-pages/internal/serving"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/vfs"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/vfs/zip"
+)
+
+var instance = disk.New(vfs.Instrumented(zip.New()))
+
+// Instance returns a serving instance that is capable of reading files
+// from a zip archives opened from a URL, most likely stored in object storage
+func Instance() serving.Serving {
+ return instance
+}
diff --git a/internal/serving/disk/zip/serving_test.go b/internal/serving/disk/zip/serving_test.go
new file mode 100644
index 00000000..14fdabdf
--- /dev/null
+++ b/internal/serving/disk/zip/serving_test.go
@@ -0,0 +1,59 @@
+package zip
+
+import (
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/serving"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/testhelpers"
+)
+
+func TestZip_ServeFileHTTP(t *testing.T) {
+ testServerURL, cleanup := newZipFileServerURL(t, "group/zip.gitlab.io/public.zip")
+ defer cleanup()
+
+ s := Instance()
+ w := httptest.NewRecorder()
+ r := httptest.NewRequest("GET", "http://zip.gitlab.io/zip/index.html", nil)
+ handler := serving.Handler{
+ Writer: w,
+ Request: r,
+ LookupPath: &serving.LookupPath{
+ Prefix: "",
+ Path: testServerURL + "/public.zip",
+ },
+ SubPath: "/index.html",
+ }
+
+ require.True(t, s.ServeFileHTTP(handler))
+
+ resp := w.Result()
+ defer resp.Body.Close()
+
+ require.Equal(t, http.StatusOK, resp.StatusCode)
+ body, err := ioutil.ReadAll(resp.Body)
+ require.NoError(t, err)
+
+ require.Contains(t, string(body), "zip.gitlab.io/project/index.html\n")
+}
+
+var chdirSet = false
+
+func newZipFileServerURL(t *testing.T, zipFilePath string) (string, func()) {
+ t.Helper()
+
+ chdir := testhelpers.ChdirInPath(t, "../../../../shared/pages", &chdirSet)
+
+ testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ http.ServeFile(w, r, zipFilePath)
+ }))
+
+ return testServer.URL, func() {
+ chdir()
+ testServer.Close()
+ }
+}
diff --git a/internal/source/gitlab/factory.go b/internal/source/gitlab/factory.go
index b5ab367b..39193a16 100644
--- a/internal/source/gitlab/factory.go
+++ b/internal/source/gitlab/factory.go
@@ -1,12 +1,11 @@
package gitlab
import (
- "strings"
-
log "github.com/sirupsen/logrus"
"gitlab.com/gitlab-org/gitlab-pages/internal/serving"
"gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk/local"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/serving/disk/zip"
"gitlab.com/gitlab-org/gitlab-pages/internal/serving/serverless"
"gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/api"
)
@@ -17,7 +16,7 @@ import (
func fabricateLookupPath(size int, lookup api.LookupPath) *serving.LookupPath {
return &serving.LookupPath{
Prefix: lookup.Prefix,
- Path: strings.TrimPrefix(lookup.Source.Path, "/"),
+ Path: lookup.Source.Path,
IsNamespaceProject: (lookup.Prefix == "/" && size > 1),
IsHTTPSOnly: lookup.HTTPSOnly,
HasAccessControl: lookup.AccessControl,
@@ -32,6 +31,8 @@ func fabricateServing(lookup api.LookupPath) serving.Serving {
switch source.Type {
case "file":
return local.Instance()
+ case "zip":
+ return zip.Instance()
case "serverless":
serving, err := serverless.NewFromAPISource(source.Serverless)
if err != nil {
diff --git a/internal/testhelpers/tmpdir.go b/internal/testhelpers/tmpdir.go
index 85201238..81f0781b 100644
--- a/internal/testhelpers/tmpdir.go
+++ b/internal/testhelpers/tmpdir.go
@@ -13,7 +13,7 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/vfs/local"
)
-var fs = vfs.Instrumented(&local.VFS{}, "local")
+var fs = vfs.Instrumented(&local.VFS{})
func TmpDir(t *testing.T, pattern string) (vfs.Root, string, func()) {
tmpDir, err := ioutil.TempDir("", pattern)
diff --git a/internal/vfs/local/vfs.go b/internal/vfs/local/vfs.go
index a2eb14f7..ca74dfbe 100644
--- a/internal/vfs/local/vfs.go
+++ b/internal/vfs/local/vfs.go
@@ -35,3 +35,7 @@ func (fs VFS) Root(ctx context.Context, path string) (vfs.Root, error) {
return &Root{rootPath: rootPath}, nil
}
+
+func (fs *VFS) Name() string {
+ return "local"
+}
diff --git a/internal/vfs/vfs.go b/internal/vfs/vfs.go
index 9d9551a1..7bd51db2 100644
--- a/internal/vfs/vfs.go
+++ b/internal/vfs/vfs.go
@@ -12,23 +12,23 @@ 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)
+ Name() string
}
-func Instrumented(fs VFS, name string) VFS {
- return &instrumentedVFS{fs: fs, name: name}
+func Instrumented(fs VFS) VFS {
+ return &instrumentedVFS{fs: fs}
}
type instrumentedVFS struct {
- fs VFS
- name string
+ fs VFS
}
func (i *instrumentedVFS) increment(operation string, err error) {
- metrics.VFSOperations.WithLabelValues(i.name, operation, strconv.FormatBool(err == nil)).Inc()
+ metrics.VFSOperations.WithLabelValues(i.fs.Name(), operation, strconv.FormatBool(err == nil)).Inc()
}
func (i *instrumentedVFS) log() *log.Entry {
- return log.WithField("vfs", i.name)
+ return log.WithField("vfs", i.fs.Name())
}
func (i *instrumentedVFS) Root(ctx context.Context, path string) (Root, error) {
@@ -44,5 +44,9 @@ func (i *instrumentedVFS) Root(ctx context.Context, path string) (Root, error) {
return nil, err
}
- return &instrumentedRoot{root: root, name: i.name, rootPath: path}, nil
+ return &instrumentedRoot{root: root, name: i.fs.Name(), rootPath: path}, nil
+}
+
+func (i *instrumentedVFS) Name() string {
+ return i.fs.Name()
}
diff --git a/internal/vfs/zip/archive.go b/internal/vfs/zip/archive.go
index ca9b778f..ce7d2ec8 100644
--- a/internal/vfs/zip/archive.go
+++ b/internal/vfs/zip/archive.go
@@ -59,6 +59,14 @@ func newArchive(path string, openTimeout time.Duration) *zipArchive {
}
func (a *zipArchive) openArchive(parentCtx context.Context) error {
+ // return early if openArchive was done already in a concurrent request
+ select {
+ case <-a.done:
+ return a.err
+
+ default:
+ }
+
ctx, cancel := context.WithTimeout(parentCtx, a.openTimeout)
defer cancel()
diff --git a/internal/vfs/zip/vfs.go b/internal/vfs/zip/vfs.go
new file mode 100644
index 00000000..283e01c2
--- /dev/null
+++ b/internal/vfs/zip/vfs.go
@@ -0,0 +1,90 @@
+package zip
+
+import (
+ "context"
+ "errors"
+ "net/url"
+ "time"
+
+ "github.com/patrickmn/go-cache"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/vfs"
+)
+
+const (
+ // TODO: make these configurable https://gitlab.com/gitlab-org/gitlab-pages/-/issues/464
+ defaultCacheExpirationInterval = time.Minute
+ defaultCacheCleanupInterval = time.Minute / 2
+ defaultCacheRefreshInterval = time.Minute / 2
+)
+
+var (
+ errAlreadyCached = errors.New("archive already cached")
+)
+
+// zipVFS is a simple cached implementation of the vfs.VFS interface
+type zipVFS struct {
+ cache *cache.Cache
+}
+
+// New creates a zipVFS instance that can be used by a serving request
+func New() vfs.VFS {
+ return &zipVFS{
+ // TODO: add cache operation callbacks https://gitlab.com/gitlab-org/gitlab-pages/-/issues/465
+ cache: cache.New(defaultCacheExpirationInterval, defaultCacheCleanupInterval),
+ }
+}
+
+// Root opens an archive given a URL path and returns an instance of zipArchive
+// that implements the vfs.VFS interface.
+// To avoid using locks, the findOrOpenArchive function runs inside of a for
+// loop until an archive is either found or created and saved.
+// 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) {
+ urlPath, err := url.Parse(path)
+ if err != nil {
+ return nil, err
+ }
+
+ // we do it in loop to not use any additional locks
+ for {
+ root, err := fs.findOrOpenArchive(ctx, urlPath.String())
+ if err == errAlreadyCached {
+ continue
+ }
+
+ return root, err
+ }
+}
+
+func (fs *zipVFS) Name() string {
+ return "zip"
+}
+
+// findOrOpenArchive if found in fs.cache refresh if needed and return it.
+// otherwise open the archive and try to save it, if saving fails it's because
+// the archive has already been cached (e.g. by another concurrent request)
+func (fs *zipVFS) findOrOpenArchive(ctx context.Context, path string) (*zipArchive, error) {
+ archive, expiry, found := fs.cache.GetWithExpiration(path)
+ if found {
+ // TODO: do not refreshed errored archives https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/351
+ if time.Until(expiry) < defaultCacheRefreshInterval {
+ // refresh item
+ fs.cache.SetDefault(path, archive)
+ }
+ } else {
+ archive = newArchive(path, DefaultOpenTimeout)
+
+ // if adding the archive to the cache fails it means it's already been added before
+ // this is done to find concurrent additions.
+ if fs.cache.Add(path, archive, cache.DefaultExpiration) != nil {
+ return nil, errAlreadyCached
+ }
+ }
+
+ zipArchive := archive.(*zipArchive)
+ err := zipArchive.openArchive(ctx)
+ return zipArchive, err
+}
diff --git a/internal/vfs/zip/vfs_test.go b/internal/vfs/zip/vfs_test.go
new file mode 100644
index 00000000..62b5f62c
--- /dev/null
+++ b/internal/vfs/zip/vfs_test.go
@@ -0,0 +1,97 @@
+package zip
+
+import (
+ "context"
+ "io/ioutil"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestVFSRoot(t *testing.T) {
+ url, cleanup := newZipFileServerURL(t, "group/zip.gitlab.io/public.zip")
+ defer cleanup()
+
+ tests := map[string]struct {
+ path string
+ expectedErrMsg string
+ }{
+ "zip_file_exists": {
+ path: "/public.zip",
+ },
+ "zip_file_does_not_exist": {
+ path: "/unknown",
+ expectedErrMsg: "404 Not Found",
+ },
+ "invalid_url": {
+ path: "/%",
+ expectedErrMsg: "invalid URL",
+ },
+ }
+
+ vfs := New()
+
+ for name, tt := range tests {
+ t.Run(name, func(t *testing.T) {
+ root, err := vfs.Root(context.Background(), url+tt.path)
+ if tt.expectedErrMsg != "" {
+ require.Error(t, err)
+ require.Contains(t, err.Error(), tt.expectedErrMsg)
+ return
+ }
+
+ require.NoError(t, err)
+ require.IsType(t, &zipArchive{}, root)
+
+ f, err := root.Open(context.Background(), "index.html")
+ require.NoError(t, err)
+
+ content, err := ioutil.ReadAll(f)
+ require.NoError(t, err)
+ require.Equal(t, "zip.gitlab.io/project/index.html\n", string(content))
+
+ fi, err := root.Lstat(context.Background(), "index.html")
+ require.NoError(t, err)
+ require.Equal(t, "index.html", fi.Name())
+
+ link, err := root.Readlink(context.Background(), "symlink.html")
+ require.NoError(t, err)
+ require.Equal(t, "subdir/linked.html", link)
+ })
+ }
+}
+
+func TestVFSFindOrOpenArchiveConcurrentAccess(t *testing.T) {
+ testServerURL, cleanup := newZipFileServerURL(t, "group/zip.gitlab.io/public.zip")
+ defer cleanup()
+
+ path := testServerURL + "/public.zip"
+
+ vfs := New().(*zipVFS)
+ root, err := vfs.Root(context.Background(), path)
+ require.NoError(t, err)
+
+ done := make(chan struct{})
+ defer close(done)
+
+ // Try to hit a condition between the invocation
+ // of cache.GetWithExpiration and cache.Add
+ go func() {
+ for {
+ select {
+ case <-done:
+ return
+
+ default:
+ vfs.cache.Flush()
+ vfs.cache.SetDefault(path, root)
+ }
+ }
+ }()
+
+ require.Eventually(t, func() bool {
+ _, err := vfs.findOrOpenArchive(context.Background(), path)
+ return err == errAlreadyCached
+ }, time.Second, time.Nanosecond)
+}
diff --git a/metrics/metrics.go b/metrics/metrics.go
index cb4287c8..dcfac76d 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -78,13 +78,13 @@ var (
Help: "The time (in seconds) it takes to get a response from the GitLab domains API",
}, []string{"status_code"})
- // DiskServingFileSize metric for file size serving. serving_types: disk and object_storage
- DiskServingFileSize = prometheus.NewHistogram(prometheus.HistogramOpts{
+ // DiskServingFileSize metric for file size serving. Includes a vfs_name (local or zip).
+ DiskServingFileSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: "gitlab_pages_disk_serving_file_size_bytes",
Help: "The size in bytes for each file that has been served",
// From 1B to 100MB in *10 increments (1 10 100 1,000 10,000 100,000 1'000,000 10'000,000 100'000,000)
Buckets: prometheus.ExponentialBuckets(1.0, 10.0, 9),
- })
+ }, []string{"vfs_name"})
// ServingTime metric for time taken to find a file serving it or not found.
ServingTime = prometheus.NewHistogram(prometheus.HistogramOpts{
diff --git a/shared/lookups/zip.gitlab.io.json b/shared/lookups/zip.gitlab.io.json
new file mode 100644
index 00000000..cf755a58
--- /dev/null
+++ b/shared/lookups/zip.gitlab.io.json
@@ -0,0 +1,16 @@
+{
+ "certificate": "",
+ "key": "",
+ "lookup_paths": [
+ {
+ "access_control": false,
+ "https_only": false,
+ "prefix": "/",
+ "project_id": 123,
+ "source": {
+ "path": "http://127.0.0.1:37003/public.zip",
+ "type": "zip"
+ }
+ }
+ ]
+}