snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
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
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
This commit is contained in:
342
weed/replication/sink/filersink/filer_sink_test.go
Normal file
342
weed/replication/sink/filersink/filer_sink_test.go
Normal file
@@ -0,0 +1,342 @@
|
||||
package filersink
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
)
|
||||
|
||||
// entry builds a file entry with the given mtime and size for the update-action
|
||||
// decision, which only looks at those two attributes.
|
||||
func entry(mtime int64, size uint64) *filer_pb.Entry {
|
||||
return &filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: mtime, FileSize: size}}
|
||||
}
|
||||
|
||||
// entryNs adds a sub-second component to check same-second ordering.
|
||||
func entryNs(mtime int64, ns int32, size uint64) *filer_pb.Entry {
|
||||
return &filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: mtime, MtimeNs: ns, FileSize: size}}
|
||||
}
|
||||
|
||||
// entryChunks builds an entry with nChunks contiguous 1-byte chunks at a given
|
||||
// mtime, so coveredBytes == nChunks. Different counts give different coverage,
|
||||
// which is what the truncation-repair routing keys on.
|
||||
func entryChunks(mtime int64, nChunks int) *filer_pb.Entry {
|
||||
e := &filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: mtime, FileSize: uint64(nChunks)}}
|
||||
for i := range nChunks {
|
||||
e.Chunks = append(e.Chunks, &filer_pb.FileChunk{
|
||||
FileId: "3," + string(rune('a'+i)), Offset: int64(i), Size: 1,
|
||||
})
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// entryMd5 is like entryChunks but also sets attr.Md5, so filer.ETag returns the
|
||||
// stored Md5 (the S3 PutObject case) instead of a chunk-derived ETag. Used to show
|
||||
// that coverage — not the Md5-backed ETag — drives the truncation decision.
|
||||
func entryMd5(mtime int64, nChunks int, md5 []byte) *filer_pb.Entry {
|
||||
e := entryChunks(mtime, nChunks)
|
||||
e.Attributes.Md5 = md5
|
||||
return e
|
||||
}
|
||||
|
||||
// srcEntry builds a source-side entry at mtime whose chunks carry the given
|
||||
// FileIds — the identities a backup destination's SourceFileId is matched against.
|
||||
func srcEntry(mtime int64, fids ...string) *filer_pb.Entry {
|
||||
e := &filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: mtime}}
|
||||
for i, f := range fids {
|
||||
e.Chunks = append(e.Chunks, &filer_pb.FileChunk{FileId: f, Offset: int64(i), Size: 1})
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// dstEntry builds a destination entry at mtime. An empty src marks a chunk
|
||||
// written out-of-band (no SourceFileId, e.g. rsync/direct); otherwise it is a
|
||||
// backup chunk copied from that source FileId.
|
||||
func dstEntry(mtime int64, srcFids ...string) *filer_pb.Entry {
|
||||
e := &filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: mtime}}
|
||||
for i, s := range srcFids {
|
||||
e.Chunks = append(e.Chunks, &filer_pb.FileChunk{FileId: "dst", SourceFileId: s, Offset: int64(i), Size: 1})
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// ivEntry builds an entry at mtime with chunks at the given [offset,end) byte
|
||||
// ranges — for coverage/containment tests where exact ranges matter.
|
||||
func ivEntry(mtime int64, ivs ...[2]int64) *filer_pb.Entry {
|
||||
e := &filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: mtime}}
|
||||
for _, iv := range ivs {
|
||||
e.Chunks = append(e.Chunks, &filer_pb.FileChunk{Offset: iv[0], Size: uint64(iv[1] - iv[0])})
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// getEntryMtimeNs must order versions written within the same second, which
|
||||
// plain second-grained mtime cannot. It also must be nil-safe.
|
||||
func TestGetEntryMtimeNs(t *testing.T) {
|
||||
if got := getEntryMtimeNs(nil); got != 0 {
|
||||
t.Fatalf("nil entry: got %d, want 0", got)
|
||||
}
|
||||
if got := getEntryMtimeNs(&filer_pb.Entry{}); got != 0 {
|
||||
t.Fatalf("nil attributes: got %d, want 0", got)
|
||||
}
|
||||
|
||||
mk := func(sec int64, ns int32) *filer_pb.Entry {
|
||||
return &filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: sec, MtimeNs: ns}}
|
||||
}
|
||||
if got, want := getEntryMtimeNs(mk(5, 200)), int64(5_000_000_200); got != want {
|
||||
t.Fatalf("full-ns: got %d, want %d", got, want)
|
||||
}
|
||||
// same second, later sub-second component must compare strictly greater
|
||||
if !(getEntryMtimeNs(mk(5, 200)) > getEntryMtimeNs(mk(5, 100))) {
|
||||
t.Fatalf("same-second ordering failed: %d not > %d",
|
||||
getEntryMtimeNs(mk(5, 200)), getEntryMtimeNs(mk(5, 100)))
|
||||
}
|
||||
}
|
||||
|
||||
// onCorruptChunk must surface the error, not skip, when it can't confirm
|
||||
// supersession (no source filer here) — otherwise it could drop a live file.
|
||||
func TestOnCorruptChunkRefusesWhenSupersessionUnconfirmed(t *testing.T) {
|
||||
fs := &FilerSink{} // filerSource nil => hasSourceNewerVersion is always false
|
||||
sentinel := errors.New("corrupt chunk: read 0 bytes, source says 107")
|
||||
|
||||
got := fs.onCorruptChunk("/buckets/x/config",
|
||||
&filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: 5, MtimeNs: 200}},
|
||||
sentinel)
|
||||
|
||||
if !errors.Is(got, sentinel) {
|
||||
t.Fatalf("expected the error to be returned (refuse), got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// onReplicateChunkError must NOT swallow a transient/partial replicate failure
|
||||
// (a volume-id lookup race or unretried manifest-chunk resolve during a write
|
||||
// burst) when it cannot confirm the source has moved on. Here filerSource is
|
||||
// nil, so hasSourceNewerVersion is always false — the "supersession unconfirmed"
|
||||
// case. Returning nil would let the metadata subscription advance its offset
|
||||
// past an event whose file was never written, silently dropping it (the
|
||||
// filer.backup partial-landing bug). It must propagate so the offset stays put
|
||||
// and the event is reprocessed. Genuine source-404 (gone/superseded) skipping is
|
||||
// covered by TestSourceSupersedes, which gates the return-nil branch.
|
||||
func TestOnReplicateChunkErrorPropagatesWhenSupersessionUnconfirmed(t *testing.T) {
|
||||
fs := &FilerSink{} // filerSource nil => hasSourceNewerVersion always false
|
||||
sentinel := errors.New("replicate manifest data chunks 7,02def: volume id 7 not found")
|
||||
|
||||
got := fs.onReplicateChunkError("/buckets/x/model.pt",
|
||||
&filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: 5, MtimeNs: 200}},
|
||||
sentinel)
|
||||
|
||||
if !errors.Is(got, sentinel) {
|
||||
t.Fatalf("expected the transient replicate error to be propagated (not swallowed), got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// chooseUpdateAction decides skip vs repair vs normal. A destination left
|
||||
// truncated by an earlier failed replication can carry a newer mtime (the
|
||||
// source preserved an old mtime while the partial copy was written recently),
|
||||
// so a pure mtime guard would strand the corruption. A shorter destination with
|
||||
// a newer mtime must be repaired, not skipped.
|
||||
func TestChooseUpdateAction(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existing *filer_pb.Entry
|
||||
oldEntry *filer_pb.Entry
|
||||
incoming *filer_pb.Entry
|
||||
want updateAction
|
||||
}{
|
||||
{"nil existing entry", nil, entry(100, 100), entry(200, 100), updateNormal},
|
||||
{"destination older, catching up", entry(100, 50), entry(100, 50), entry(200, 100), updateNormal},
|
||||
{"same mtime", entry(200, 50), entry(200, 50), entry(200, 100), updateNormal},
|
||||
{"destination newer and complete", entry(300, 100), entry(100, 100), entry(200, 100), updateSkip},
|
||||
{"destination newer and larger", entry(300, 200), entry(100, 100), entry(200, 100), updateSkip},
|
||||
// out-of-order: a newer (smaller) destination must NOT be rolled back by an
|
||||
// older, larger replayed event — even though it does not cover oldEntry.
|
||||
{"newer smaller dest, older larger event -> skip", ivEntry(300, [2]int64{0, 5}), ivEntry(100, [2]int64{0, 10}), ivEntry(100, [2]int64{0, 10}), updateSkip},
|
||||
// same second: sub-second ordering must still pick the winner
|
||||
{"same second, destination newer and complete", entryNs(5, 200, 100), entryNs(5, 100, 100), entryNs(5, 100, 100), updateSkip},
|
||||
{"same second, destination older", entryNs(5, 100, 100), entryNs(5, 100, 100), entryNs(5, 200, 100), updateNormal},
|
||||
// chunk-incompleteness (the truncation bug): destination is at/behind the
|
||||
// incoming mtime but covers fewer bytes than oldEntry, so the incremental
|
||||
// diff cannot heal it — must repair in full, not append. entryChunks(n) has
|
||||
// n contiguous 1-byte chunks → coverage n.
|
||||
{"behind, covers less than oldEntry -> repair", entryChunks(200, 2), entryChunks(200, 4), entryChunks(200, 4), updateRepair},
|
||||
{"behind, matches oldEntry coverage -> normal", entryChunks(200, 3), entryChunks(200, 3), entryChunks(200, 5), updateNormal},
|
||||
{"behind, exceeds oldEntry coverage -> normal", entryChunks(200, 5), entryChunks(200, 3), entryChunks(200, 5), updateNormal},
|
||||
// range containment, not scalar coverage: equal total bytes but a missing
|
||||
// range (the destination has an extra range at another offset) → repair.
|
||||
{"behind, equal total but range gap -> repair", ivEntry(200, [2]int64{0, 10}, [2]int64{30, 40}), ivEntry(200, [2]int64{0, 20}), ivEntry(200, [2]int64{0, 20}), updateRepair},
|
||||
// same-range stale: full coverage but a backup-written chunk whose source
|
||||
// oldEntry no longer references → repair only when the destination is
|
||||
// provably older. Equal-mtime is deferred (cannot order same-second
|
||||
// versions). Out-of-band (rsync) chunks are never stale-checked.
|
||||
{"older, stale backup chunk -> repair", dstEntry(100, "A", "X"), srcEntry(100, "A", "B"), srcEntry(200, "A", "B"), updateRepair},
|
||||
{"equal-mtime stale -> deferred (normal)", dstEntry(200, "A", "X"), srcEntry(200, "A", "B"), srcEntry(200, "A", "B"), updateNormal},
|
||||
{"older, rsync chunks -> normal", dstEntry(100, "", ""), srcEntry(100, "A", "B"), srcEntry(200, "A", "B"), updateNormal},
|
||||
// in-sync fast path: destination holds exactly oldEntry's chunks → normal
|
||||
{"in-sync (fast path) -> normal", dstEntry(100, "A", "B"), srcEntry(100, "A", "B"), srcEntry(200, "A", "B"), updateNormal},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := chooseUpdateAction(tc.existing, tc.oldEntry, tc.incoming); got != tc.want {
|
||||
t.Fatalf("chooseUpdateAction = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// coversReference must verify range containment, not just equal total bytes: a
|
||||
// destination with the same byte count but a missing range (extra chunk elsewhere)
|
||||
// does not cover the reference.
|
||||
func TestCoversReference(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
existing, reference *filer_pb.Entry
|
||||
want bool
|
||||
}{
|
||||
{"empty reference", ivEntry(0), ivEntry(0), true},
|
||||
{"reference no chunks", ivEntry(0, [2]int64{0, 10}), ivEntry(0), true},
|
||||
{"exact", ivEntry(0, [2]int64{0, 10}), ivEntry(0, [2]int64{0, 10}), true},
|
||||
{"superset", ivEntry(0, [2]int64{0, 20}), ivEntry(0, [2]int64{0, 10}), true},
|
||||
{"truncated tail", ivEntry(0, [2]int64{0, 5}), ivEntry(0, [2]int64{0, 10}), false},
|
||||
{"equal total, range gap", ivEntry(0, [2]int64{0, 10}, [2]int64{30, 40}), ivEntry(0, [2]int64{0, 20}), false},
|
||||
{"contiguous parts cover", ivEntry(0, [2]int64{0, 10}, [2]int64{10, 20}), ivEntry(0, [2]int64{0, 20}), true},
|
||||
{"overlapping parts cover", ivEntry(0, [2]int64{0, 12}, [2]int64{8, 20}), ivEntry(0, [2]int64{0, 20}), true},
|
||||
{"existing empty, reference has bytes", ivEntry(0), ivEntry(0, [2]int64{0, 1}), false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := coversReference(tc.existing, tc.reference); got != tc.want {
|
||||
t.Fatalf("coversReference = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// destinationSatisfiesSource gates CreateEntry's skip. Coverage/identity must be
|
||||
// checked before ETag: a truncated or stale destination whose attr.Md5 still
|
||||
// matches the source (so filer.ETag is equal) must NOT be skipped, or
|
||||
// -initialSnapshot would leave it permanently diverged.
|
||||
func TestDestinationSatisfiesSource(t *testing.T) {
|
||||
md5A := []byte("0123456789abcdef")
|
||||
md5B := []byte("fedcba9876543210")
|
||||
cases := []struct {
|
||||
name string
|
||||
existing, incoming *filer_pb.Entry
|
||||
want bool
|
||||
}{
|
||||
{"nil existing", nil, entryChunks(200, 4), false},
|
||||
{"identical complete -> skip", entryChunks(200, 4), entryChunks(200, 4), true},
|
||||
{"truncated (no md5) -> replicate", entryChunks(200, 2), entryChunks(200, 4), false},
|
||||
// regression: same attr.Md5 (equal filer.ETag) but fewer chunks must repair
|
||||
{"md5-backed truncated -> replicate", entryMd5(200, 2, md5A), entryMd5(200, 4, md5A), false},
|
||||
{"md5 equal and complete -> skip", entryMd5(200, 4, md5A), entryMd5(200, 4, md5A), true},
|
||||
// complete but different content: keep newer, replace older
|
||||
{"complete, dest newer -> skip", entryMd5(300, 4, md5A), entryMd5(200, 4, md5B), true},
|
||||
{"complete, dest older -> replicate", entryMd5(200, 4, md5A), entryMd5(300, 4, md5B), false},
|
||||
// same-range stale backup chunk: replicate only when provably older; an
|
||||
// equal-mtime stale dest is deferred (skip), and rsync chunks are not
|
||||
// stale-checked.
|
||||
{"older, stale backup chunk -> replicate", dstEntry(100, "A", "X"), srcEntry(200, "A", "B"), false},
|
||||
{"equal-mtime stale -> skip (deferred)", dstEntry(200, "A", "X"), srcEntry(200, "A", "B"), true},
|
||||
{"rsync complete -> skip", dstEntry(200, "", ""), srcEntry(200, "A", "B"), true},
|
||||
{"in-sync (fast path) -> skip", dstEntry(200, "A", "B"), srcEntry(200, "A", "B"), true},
|
||||
// out-of-order: newer (smaller) destination must not be rolled back by an
|
||||
// older, larger source replay.
|
||||
{"newer smaller dest, older larger source -> skip", ivEntry(300, [2]int64{0, 5}), ivEntry(100, [2]int64{0, 10}), true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := destinationSatisfiesSource(tc.existing, tc.incoming); got != tc.want {
|
||||
t.Fatalf("destinationSatisfiesSource = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// destinationMatchesReference is the allocation-free fast path: true only when the
|
||||
// destination holds exactly the reference's chunks in order (by SourceFileId). It
|
||||
// must be conservative — never a false positive — so reorders, count mismatches,
|
||||
// and out-of-band (no SourceFileId) chunks all return false.
|
||||
func TestDestinationMatchesReference(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
existing, reference *filer_pb.Entry
|
||||
want bool
|
||||
}{
|
||||
{"both empty", dstEntry(0), srcEntry(0), true},
|
||||
{"exact in order", dstEntry(0, "A", "B"), srcEntry(0, "A", "B"), true},
|
||||
{"count mismatch", dstEntry(0, "A"), srcEntry(0, "A", "B"), false},
|
||||
{"reordered", dstEntry(0, "B", "A"), srcEntry(0, "A", "B"), false},
|
||||
{"stale identity", dstEntry(0, "A", "X"), srcEntry(0, "A", "B"), false},
|
||||
{"out-of-band chunk", dstEntry(0, "A", ""), srcEntry(0, "A", "B"), false},
|
||||
// Same identity but a different byte range must not fast-path as a match,
|
||||
// so the precise checks still run if replication ever re-chunks.
|
||||
{"shifted offset, same identity",
|
||||
&filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: 0}, Chunks: []*filer_pb.FileChunk{
|
||||
{FileId: "dst", SourceFileId: "A", Offset: 0, Size: 1},
|
||||
{FileId: "dst", SourceFileId: "B", Offset: 5, Size: 1},
|
||||
}}, srcEntry(0, "A", "B"), false},
|
||||
{"larger size, same identity",
|
||||
&filer_pb.Entry{Attributes: &filer_pb.FuseAttributes{Mtime: 0}, Chunks: []*filer_pb.FileChunk{
|
||||
{FileId: "dst", SourceFileId: "A", Offset: 0, Size: 2},
|
||||
}}, srcEntry(0, "A"), false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := destinationMatchesReference(tc.existing, tc.reference); got != tc.want {
|
||||
t.Fatalf("destinationMatchesReference = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// hasStaleBackupChunk flags a destination that still holds a backup-written chunk
|
||||
// (SourceFileId set) the source reference no longer lists — a missed
|
||||
// overwrite/deletion. Out-of-band chunks (no SourceFileId, e.g. rsync) are ignored.
|
||||
func TestHasStaleBackupChunk(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
existing, reference *filer_pb.Entry
|
||||
want bool
|
||||
}{
|
||||
{"nil existing", nil, srcEntry(200, "A"), false},
|
||||
{"in sync", dstEntry(200, "A", "B"), srcEntry(200, "A", "B"), false},
|
||||
{"stale backup chunk", dstEntry(200, "A", "X"), srcEntry(200, "A", "B"), true},
|
||||
{"rsync chunks ignored", dstEntry(200, "", ""), srcEntry(200, "A", "B"), false},
|
||||
{"synced backup + rsync", dstEntry(200, "A", ""), srcEntry(200, "A", "B"), false},
|
||||
{"stale backup among rsync", dstEntry(200, "X", ""), srcEntry(200, "A", "B"), true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := hasStaleBackupChunk(tc.existing, tc.reference); got != tc.want {
|
||||
t.Fatalf("hasStaleBackupChunk = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// updatedEntryKey must resolve the incoming entry's new path. For a rename the
|
||||
// supersession check has to target newParentPath/newEntry.Name, not the old key,
|
||||
// or the renamed-away old path looks deleted on the source and skips a live event.
|
||||
func TestUpdatedEntryKey(t *testing.T) {
|
||||
named := func(name string) *filer_pb.Entry { return &filer_pb.Entry{Name: name} }
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
newParentPath string
|
||||
entry *filer_pb.Entry
|
||||
want string
|
||||
}{
|
||||
{"content update, no new parent", "/dst/a.txt", "", named("a.txt"), "/dst/a.txt"},
|
||||
{"rename same dir", "/dst/old.txt", "/dst", named("new.txt"), "/dst/new.txt"},
|
||||
{"rename to subdir", "/dst/old.txt", "/dst/sub", named("new.txt"), "/dst/sub/new.txt"},
|
||||
{"root parent", "/old.txt", "/", named("new.txt"), "/new.txt"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := updatedEntryKey(tc.key, tc.newParentPath, tc.entry); got != tc.want {
|
||||
t.Fatalf("updatedEntryKey = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user