mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 07:39:19 -05:00
A snapshot was recorded after the response by reading the metadata cache again, so a refresh that replaced the cached copy in between lost the version the request served. Record it from the served bytes before responding instead, and compare a new snapshot with the version current at its own time, so one recorded late keeps its place in the history.
190 lines
6.3 KiB
Go
190 lines
6.3 KiB
Go
package database
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func addSnapshot(t *testing.T, db *DB, name, digest string, validFrom time.Time) bool {
|
|
t.Helper()
|
|
added, err := db.AddMetadataSnapshot(&MetadataSnapshot{
|
|
Ecosystem: "alpine",
|
|
Name: name,
|
|
StoragePath: "_snapshots/alpine/" + name + "/" + digest,
|
|
ContentDigest: digest,
|
|
Size: 1,
|
|
ValidFromMS: validFrom.UnixMilli(),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("AddMetadataSnapshot: %v", err)
|
|
}
|
|
return added
|
|
}
|
|
|
|
func snapshotDigestAt(t *testing.T, db *DB, name string, at time.Time) string {
|
|
t.Helper()
|
|
snap, err := db.MetadataSnapshotAt("alpine", name, at)
|
|
if err != nil {
|
|
t.Fatalf("MetadataSnapshotAt: %v", err)
|
|
}
|
|
if snap == nil {
|
|
return ""
|
|
}
|
|
return snap.ContentDigest
|
|
}
|
|
|
|
func TestMetadataSnapshots_AddAndLookup(t *testing.T) {
|
|
runWithBothDatabases(t, func(t *testing.T, db *DB) {
|
|
day := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
|
|
if !addSnapshot(t, db, "idx", "sha256:a", day) {
|
|
t.Fatal("first snapshot not added")
|
|
}
|
|
if addSnapshot(t, db, "idx", "sha256:a", day.Add(time.Hour)) {
|
|
t.Error("unchanged digest added again")
|
|
}
|
|
if !addSnapshot(t, db, "idx", "sha256:b", day.Add(48*time.Hour)) {
|
|
t.Fatal("changed digest not added")
|
|
}
|
|
// A rollback to an earlier version is a new snapshot.
|
|
if !addSnapshot(t, db, "idx", "sha256:a", day.Add(96*time.Hour)) {
|
|
t.Fatal("rolled back digest not added")
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
at time.Time
|
|
want string
|
|
}{
|
|
{day, ""},
|
|
{day.Add(time.Minute), "sha256:a"},
|
|
{day.Add(72 * time.Hour), "sha256:b"},
|
|
{day.Add(100 * time.Hour), "sha256:a"},
|
|
} {
|
|
if got := snapshotDigestAt(t, db, "idx", tc.at); got != tc.want {
|
|
t.Errorf("snapshot at %s = %q, want %q", tc.at, got, tc.want)
|
|
}
|
|
}
|
|
latest, err := db.LatestMetadataSnapshot("alpine", "idx")
|
|
if err != nil || latest == nil || !latest.ValidFrom().Equal(day.Add(96*time.Hour)) {
|
|
t.Errorf("LatestMetadataSnapshot = %+v, %v", latest, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestMetadataSnapshots_RecordedLate adds a version after a newer one was
|
|
// recorded, as a request that fetched it first but recorded it last does. It
|
|
// is compared with the version current at its own time, so it keeps its place
|
|
// in the history.
|
|
func TestMetadataSnapshots_RecordedLate(t *testing.T) {
|
|
runWithBothDatabases(t, func(t *testing.T, db *DB) {
|
|
day := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
|
|
addSnapshot(t, db, "idx", "sha256:a", day)
|
|
addSnapshot(t, db, "idx", "sha256:c", day.Add(2*time.Hour))
|
|
if !addSnapshot(t, db, "idx", "sha256:b", day.Add(time.Hour)) {
|
|
t.Fatal("version recorded after a newer one not added")
|
|
}
|
|
if addSnapshot(t, db, "idx", "sha256:b", day.Add(90*time.Minute)) {
|
|
t.Error("version already current at that time added again")
|
|
}
|
|
for _, tc := range []struct {
|
|
at time.Time
|
|
want string
|
|
}{
|
|
{day.Add(30 * time.Minute), "sha256:a"},
|
|
{day.Add(100 * time.Minute), "sha256:b"},
|
|
{day.Add(3 * time.Hour), "sha256:c"},
|
|
} {
|
|
if got := snapshotDigestAt(t, db, "idx", tc.at); got != tc.want {
|
|
t.Errorf("snapshot at %s = %q, want %q", tc.at, got, tc.want)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMetadataSnapshots_Prune(t *testing.T) {
|
|
runWithBothDatabases(t, func(t *testing.T, db *DB) {
|
|
now := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)
|
|
addSnapshot(t, db, "idx", "sha256:a", now.AddDate(0, 0, -100))
|
|
addSnapshot(t, db, "idx", "sha256:b", now.AddDate(0, 0, -60))
|
|
addSnapshot(t, db, "idx", "sha256:c", now.AddDate(0, 0, -10))
|
|
// The only version of another index stays however old it is.
|
|
addSnapshot(t, db, "other", "sha256:x", now.AddDate(0, 0, -200))
|
|
|
|
pruned, err := db.PruneMetadataSnapshots("alpine", now.AddDate(0, 0, -30))
|
|
if err != nil {
|
|
t.Fatalf("PruneMetadataSnapshots: %v", err)
|
|
}
|
|
// a was replaced 60 days ago, b only 10 days ago.
|
|
if pruned != 1 {
|
|
t.Errorf("pruned = %d, want 1", pruned)
|
|
}
|
|
if got := snapshotDigestAt(t, db, "idx", now.AddDate(0, 0, -90)); got != "" {
|
|
t.Errorf("snapshot of a pruned day = %q, want none", got)
|
|
}
|
|
if got := snapshotDigestAt(t, db, "idx", now.AddDate(0, 0, -50)); got != "sha256:b" {
|
|
t.Errorf("snapshot 50 days ago = %q, want sha256:b", got)
|
|
}
|
|
if got := snapshotDigestAt(t, db, "other", now); got != "sha256:x" {
|
|
t.Errorf("only snapshot of other = %q, want sha256:x", got)
|
|
}
|
|
|
|
due, err := db.GetDuePendingDeletes(time.Now().Add(time.Hour), 10)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(due) != 1 || due[0] != "_snapshots/alpine/idx/sha256:a" {
|
|
t.Errorf("queued deletes = %v, want the pruned blob", due)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestMetadataSnapshots_ConcurrentPostgres records a version while another
|
|
// replica's transaction has recorded the same one and not committed yet, as
|
|
// two replicas can. The second must wait and then find it, so one row
|
|
// remains. Without the lock both inserts succeed.
|
|
func TestMetadataSnapshots_ConcurrentPostgres(t *testing.T) {
|
|
db := createTestPostgresDB(t)
|
|
if db == nil {
|
|
t.Skip("PROXY_DATABASE_URL not set, skipping postgres test")
|
|
}
|
|
defer func() { _ = db.Close() }()
|
|
const name = "v3.22/main/x86_64/APKINDEX.tar.gz"
|
|
|
|
other, err := db.Beginx()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = other.Rollback() }()
|
|
if _, err := other.Exec("SELECT pg_advisory_xact_lock($1)", metadataSnapshotLockKey("alpine", name)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := other.Exec(`INSERT INTO metadata_snapshots (ecosystem, name, storage_path, content_digest, size, valid_from_ms)
|
|
VALUES ('alpine', $1, 'other', 'sha256:a', 1, $2)`, name, time.Now().UnixMilli()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
done := make(chan bool, 1)
|
|
go func() { done <- addSnapshot(t, db, name, "sha256:a", time.Now()) }()
|
|
select {
|
|
case added := <-done:
|
|
t.Fatalf("recorded without waiting for the other replica (added=%v)", added)
|
|
case <-time.After(200 * time.Millisecond):
|
|
}
|
|
if err := other.Commit(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if added := <-done; added {
|
|
t.Error("recorded a version the other replica had just recorded")
|
|
}
|
|
var n int
|
|
if err := db.Get(&n, `SELECT COUNT(*) FROM metadata_snapshots WHERE name = $1`, name); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 1 {
|
|
t.Errorf("%d snapshot rows, want 1", n)
|
|
}
|
|
|
|
// Going back to an earlier digest is still recorded.
|
|
if !addSnapshot(t, db, name, "sha256:b", time.Now()) || !addSnapshot(t, db, name, "sha256:a", time.Now().Add(time.Millisecond)) {
|
|
t.Error("a change back to an earlier digest was not recorded")
|
|
}
|
|
}
|