Reject bare * and unknown remotes on evict, serialise with fetch lock
- return 400 for a bare * and 404 for an unknown remote - take the per-path fetch lock for single-path evicts - pass the evictor to Routes so local routes no longer carry it - test If-None-Match revalidation, ForgetPath/ForgetPrefix glob escaping
This commit is contained in:
@@ -2,28 +2,40 @@ package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "git.unkin.net/unkin/artifactapi/internal/provider/rpm"
|
||||
"git.unkin.net/unkin/artifactapi/pkg/models"
|
||||
)
|
||||
|
||||
// changingUpstream serves every path with the current revision, as a mirror
|
||||
// does after a sync replaces its repodata.
|
||||
func changingUpstream(t *testing.T) (*httptest.Server, *atomic.Value) {
|
||||
// does after a sync replaces its repodata. It answers 304 to a matching
|
||||
// If-None-Match and counts conditional requests.
|
||||
func changingUpstream(t *testing.T) (*httptest.Server, *atomic.Value, *atomic.Int32) {
|
||||
t.Helper()
|
||||
var rev atomic.Value
|
||||
var conditional atomic.Int32
|
||||
rev.Store("rev1")
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
v := rev.Load().(string)
|
||||
w.Header().Set("ETag", `"`+v+`"`)
|
||||
etag := `"` + v + `"`
|
||||
w.Header().Set("ETag", etag)
|
||||
if inm := r.Header.Get("If-None-Match"); inm != "" {
|
||||
conditional.Add(1)
|
||||
if inm == etag {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
_, _ = w.Write([]byte(v + ":" + r.URL.Path))
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
return srv, &rev
|
||||
return srv, &rev, &conditional
|
||||
}
|
||||
|
||||
func fetchBody(t *testing.T, r models.Remote, path string) string {
|
||||
@@ -41,7 +53,7 @@ func rpmRemote(t *testing.T, name, baseURL string) models.Remote {
|
||||
|
||||
func TestEvictMutableIndexRefetches(t *testing.T) {
|
||||
requireStack(t)
|
||||
srv, rev := changingUpstream(t)
|
||||
srv, rev, conditional := changingUpstream(t)
|
||||
r := rpmRemote(t, "evict-idx", srv.URL)
|
||||
const path = "8/Everything/x86_64/repodata/repomd.xml"
|
||||
|
||||
@@ -55,14 +67,64 @@ func TestEvictMutableIndexRefetches(t *testing.T) {
|
||||
if err := testEngine.Evict(context.Background(), r.Name, path); err != nil {
|
||||
t.Fatalf("evict: %v", err)
|
||||
}
|
||||
conditional.Store(0)
|
||||
if got := fetchBody(t, r, path); got != "rev2:/"+path {
|
||||
t.Fatalf("after evict = %q, want rev2", got)
|
||||
}
|
||||
if n := conditional.Load(); n != 0 {
|
||||
t.Errorf("post-evict fetch revalidated with the evicted ETag (%d conditional requests)", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvictRejectsBareWildcard(t *testing.T) {
|
||||
requireStack(t)
|
||||
srv, _, _ := changingUpstream(t)
|
||||
r := rpmRemote(t, "evict-bare", srv.URL)
|
||||
const path = "8/Everything/x86_64/repodata/repomd.xml"
|
||||
fetchBody(t, r, path)
|
||||
|
||||
var pe *ProxyError
|
||||
if err := testEngine.Evict(context.Background(), r.Name, "*"); !errors.As(err, &pe) || pe.Status != http.StatusBadRequest {
|
||||
t.Fatalf("evict * = %v, want 400", err)
|
||||
}
|
||||
if fresh, _ := testEngine.cache.CheckTTL(context.Background(), r.Name, path); !fresh {
|
||||
t.Error("bare * evicted cached keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvictUnknownRemote(t *testing.T) {
|
||||
requireStack(t)
|
||||
var pe *ProxyError
|
||||
if err := testEngine.Evict(context.Background(), "evict-no-such-remote", "a/b"); !errors.As(err, &pe) || pe.Status != http.StatusNotFound {
|
||||
t.Fatalf("evict unknown remote = %v, want 404", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvictWaitsForFetchLock(t *testing.T) {
|
||||
requireStack(t)
|
||||
srv, _, _ := changingUpstream(t)
|
||||
r := rpmRemote(t, "evict-lock", srv.URL)
|
||||
ctx := context.Background()
|
||||
const path = "8/Everything/x86_64/repodata/repomd.xml"
|
||||
if ok, err := testEngine.cache.AcquireLock(ctx, r.Name, path, time.Minute); !ok || err != nil {
|
||||
t.Fatalf("acquire: %v %v", ok, err)
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- testEngine.Evict(ctx, r.Name, path) }()
|
||||
select {
|
||||
case err := <-done:
|
||||
t.Fatalf("evict returned while fetch lock held: %v", err)
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
}
|
||||
_ = testEngine.cache.ReleaseLock(ctx, r.Name, path)
|
||||
if err := <-done; err != nil {
|
||||
t.Fatalf("evict: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvictWildcardClearsPrefixOnly(t *testing.T) {
|
||||
requireStack(t)
|
||||
srv, rev := changingUpstream(t)
|
||||
srv, rev, _ := changingUpstream(t)
|
||||
r := rpmRemote(t, "evict-wild", srv.URL)
|
||||
const (
|
||||
repomd = "8/Everything/x86_64/repodata/repomd.xml"
|
||||
|
||||
Reference in New Issue
Block a user