Files
upstream-seaweedfs/weed/s3api/s3lifecycle/dailyrun/walker_interval_test.go
upstream-mirror 89f5be005d
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 03:23:42 +05:00

277 lines
11 KiB
Go

package dailyrun
import (
"context"
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/pb/s3_lifecycle_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3lifecycle"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3lifecycle/engine"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3lifecycle/reader"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3lifecycle/router"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// readerEventAlias keeps the test free of the reader import collision
// in the recovery test file when we need a typed nil channel.
type readerEventAlias = reader.Event
// TestWalkerDue covers the throttle decision matrix in isolation.
func TestWalkerDue(t *testing.T) {
runNow := time.Unix(1_700_000_000, 0).UTC()
cases := []struct {
name string
lastWalkedNs int64
interval time.Duration
want bool
}{
// interval=0 keeps the prior "fire every pass" semantics — the
// in-repo integration tests and the s3tests fast driver rely on
// this so the rule-change-then-walk behavior surfaces within a
// single test runtime. Within-pass double-fire suppression
// lives in runShard's walkedThisPass local; walkerDue answers
// the persisted-state question only.
{"interval zero always due", runNow.UnixNano(), 0, true},
// LastWalkedNs=0 marks "never walked steady-state" — the post-
// upgrade cold-start case for cursor files that predate the
// LastWalkedNs field. Fire so the throttle anchor gets seeded.
{"never walked is due", 0, time.Hour, true},
// Throttle on: interval elapsed → due.
{"interval elapsed", runNow.Add(-2 * time.Hour).UnixNano(), time.Hour, true},
{"interval exactly elapsed", runNow.Add(-time.Hour).UnixNano(), time.Hour, true},
// Throttle on: not yet → skip.
{"interval not yet elapsed", runNow.Add(-30 * time.Minute).UnixNano(), time.Hour, false},
// Future LastWalkedNs (clock skew or replay of an older runNow
// against a newer cursor) must not panic and must not fire —
// treat as "throttled" because the cursor claims a future walk.
{"future lastWalked", runNow.Add(time.Hour).UnixNano(), time.Hour, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.want, walkerDue(tc.lastWalkedNs, runNow, tc.interval))
})
}
}
// TestRunShard_WalkerThrottle confirms two back-to-back runShard
// invocations only fire the steady-state walker once when the second
// pass starts inside the WalkerInterval window. The interval-zero
// control case keeps firing on every pass — same shape, different
// expectation — pinning the prior behavior so a default-zero config
// (tests, in-repo integration) doesn't regress.
func TestRunShard_WalkerThrottle(t *testing.T) {
cases := []struct {
name string
interval time.Duration
secondPassAfter time.Duration
wantTotalCalls int
wantSecondAdvNs bool // did LastWalkedNs change between pass 1 and pass 2?
}{
{"interval=0 fires every pass", 0, 30 * time.Second, 2, true},
{"throttled: second pass within interval", time.Hour, 30 * time.Second, 1, false},
{"throttled: second pass past interval", time.Hour, 2 * time.Hour, 2, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
snap := snapshotWithRule(t, 30) // replay-eligible only; steady-state walker would normally skip
// Force a walker-only partition so the steady-state branch
// has something to walk: set retentionWindow=0 in cfg, which
// makes promoted non-empty and walkView non-empty.
p := newMemPersister()
calls := 0
cfg := Config{
Persister: p,
Walker: func(_ context.Context, _ *engine.Snapshot, _ int) error {
calls++
return nil
},
RetentionWindow: -1, // negative sentinel: falls back to maxTTL — leaves walkView empty
WalkerInterval: tc.interval,
}
// Pre-seed cursor matching snap's hashes so cold-start /
// recovery branches don't fire and we measure ONLY the
// steady-state walker.
rsh := engine.ReplayContentHash(snap)
promoted := engine.PromotedHash(snap, engine.MaxEffectiveTTL(snap))
runNow := time.Unix(1_700_000_000, 0).UTC()
require.NoError(t, p.Save(context.Background(), 0, Cursor{
TsNs: runNow.UnixNano(),
RuleSetHash: rsh,
PromotedHash: promoted,
}))
// With the snapshot's only rule being replay-eligible, the
// steady-state walkView is empty and the walker won't fire
// regardless of throttle. Use a snapshot that has BOTH a
// replay rule AND a scan-only walker rule by forcing
// retentionWindow to a very small value via cfg.
cfg.RetentionWindow = time.Nanosecond
// Recompute promoted with the test retention so persisted
// hashes match what runShard sees on each pass.
snapPromoted := engine.PromotedHash(snap, time.Nanosecond)
require.NoError(t, p.Save(context.Background(), 0, Cursor{
TsNs: runNow.UnixNano(),
RuleSetHash: rsh,
PromotedHash: snapPromoted,
}))
// runShard reaches drainShardEvents after the steady-state
// walker fires (replay-eligible rules present). A nil events
// channel would block forever; a closed one returns
// immediately so drain exits cleanly and the cursor save
// still records LastWalkedNs.
closedEvents := make(chan *readerEventAlias)
close(closedEvents)
// Pass 1.
require.NoError(t, runShard(context.Background(), cfg, snap, runNow, 0, closedEvents))
afterPass1, _, _ := p.Load(context.Background(), 0)
require.Equal(t, 1, calls, "pass 1 must fire the walker (cold-start anchor)")
require.Equal(t, runNow.UnixNano(), afterPass1.LastWalkedNs)
// Pass 2.
runNow2 := runNow.Add(tc.secondPassAfter)
closedEvents2 := make(chan *readerEventAlias)
close(closedEvents2)
require.NoError(t, runShard(context.Background(), cfg, snap, runNow2, 0, closedEvents2))
afterPass2, _, _ := p.Load(context.Background(), 0)
assert.Equal(t, tc.wantTotalCalls, calls, "walker calls after 2 passes")
if tc.wantSecondAdvNs {
assert.Equal(t, runNow2.UnixNano(), afterPass2.LastWalkedNs, "throttle allowed the second walk")
} else {
assert.Equal(t, runNow.UnixNano(), afterPass2.LastWalkedNs, "throttle suppressed the second walk; anchor unchanged")
}
})
}
}
// TestValidate_RejectsNegativeWalkerInterval pins the loud-failure
// contract for an embedder or test that constructs dailyrun.Config
// directly with a negative WalkerInterval. The admin-config parser
// clamps negative input to zero (worker/tasks/s3_lifecycle/config.go),
// but a caller bypassing that parser would otherwise get the silent
// fall-through-to-walk-every-pass behavior the throttle is trying to
// prevent.
func TestValidate_RejectsNegativeWalkerInterval(t *testing.T) {
// validate only checks for nil fields, not behavior — type-assert
// each interface to its zero value via a stub. The stubs never run.
cfg := validatableConfig()
cfg.WalkerInterval = -time.Hour
err := validate(cfg)
require.Error(t, err)
assert.Contains(t, err.Error(), "WalkerInterval")
// And the zero sentinel still passes.
cfg.WalkerInterval = 0
require.NoError(t, validate(cfg))
}
// validatableConfig builds a minimal dailyrun.Config that passes the
// nil checks in validate() so individual fields can be poked. The
// stubs intentionally don't implement any meaningful behavior — they
// only need to be non-nil interface values.
func validatableConfig() Config {
return Config{
Engine: engine.New(),
FilerClient: stubFilerClient{},
Client: stubLifecycleClient{},
Persister: newMemPersister(),
Lister: stubSiblingLister{},
BucketsPath: "/buckets",
}
}
type stubFilerClient struct{ filer_pb.SeaweedFilerClient }
type stubLifecycleClient struct{}
func (stubLifecycleClient) LifecycleDelete(_ context.Context, _ *s3_lifecycle_pb.LifecycleDeleteRequest) (*s3_lifecycle_pb.LifecycleDeleteResponse, error) {
return nil, nil
}
type stubSiblingLister struct{}
func (stubSiblingLister) Survivors(_ context.Context, _, _ string) (router.Survivors, error) {
return router.Survivors{}, nil
}
func (stubSiblingLister) LookupVersion(_ context.Context, _, _, _ string) (*filer_pb.Entry, error) {
return nil, nil
}
func (stubSiblingLister) ListVersions(_ context.Context, _, _ string) ([]*filer_pb.Entry, error) {
return nil, nil
}
func (stubSiblingLister) LookupNullVersion(_ context.Context, _, _ string) (*filer_pb.Entry, bool, error) {
return nil, false, nil
}
// TestRunShard_ColdStartDoesNotDoubleWalk pins the within-pass guard
// in runShard's walkedThisPass local. Before the guard, a cold-start
// pass with WalkerInterval=0 fired the walker twice in one pass:
// once via the mustWalkColdStart branch (with RecoveryView) and again
// immediately via the steady-state branch (with the per-shard walk
// view, which is a subset of RecoveryView). The second walk added no
// coverage and burned a full bucket scan. Guard against regression.
func TestRunShard_ColdStartDoesNotDoubleWalk(t *testing.T) {
snap := snapshotWithRule(t, 30)
p := newMemPersister()
// No persisted cursor → mustWalkColdStart=true. WalkerInterval=0
// would, before the fix, allow the steady-state branch to fire
// again with no time elapsed.
calls := 0
cfg := Config{
Persister: p,
Walker: func(_ context.Context, _ *engine.Snapshot, _ int) error {
calls++
return nil
},
WalkerInterval: 0,
}
runNow := time.Unix(1_700_000_000, 0).UTC()
closedEvents := make(chan *readerEventAlias)
close(closedEvents)
require.NoError(t, runShard(context.Background(), cfg, snap, runNow, 0, closedEvents))
assert.Equal(t, 1, calls, "cold-start walker must fire exactly once per pass even with interval=0")
}
// TestRunShard_RecoveryWalkerSetsLastWalkedAnchor pins that the
// unconditional recovery-branch walker fire still updates the
// LastWalkedNs anchor so the NEXT pass's throttle starts counting
// from this walk rather than re-firing immediately.
func TestRunShard_RecoveryWalkerSetsLastWalkedAnchor(t *testing.T) {
snap := snapshotWithRule(t, 30)
p := newMemPersister()
var stale [32]byte
for i := range stale {
stale[i] = 0xAA
}
require.NoError(t, p.Save(context.Background(), 5, Cursor{
TsNs: 1234,
RuleSetHash: stale,
LastWalkedNs: 0, // never walked anchor
}))
calls := 0
cfg := Config{
Persister: p,
Walker: func(_ context.Context, _ *engine.Snapshot, _ int) error {
calls++
return nil
},
WalkerInterval: time.Hour,
}
runNow := time.Unix(1_700_000_000, 0).UTC()
require.NoError(t, runShard(context.Background(), cfg, snap, runNow, 5, nil))
require.Equal(t, 1, calls, "recovery branch fires the walker unconditionally")
got, ok, _ := p.Load(context.Background(), 5)
require.True(t, ok)
assert.Equal(t, runNow.UnixNano(), got.LastWalkedNs,
"recovery walk must update LastWalkedNs so the steady-state pass after it can throttle")
}
// Compile-time check that the documented sentinel passes the type system.
var _ = s3lifecycle.ShardCount