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:
517
weed/operation/upload_content_test.go
Normal file
517
weed/operation/upload_content_test.go
Normal file
@@ -0,0 +1,517 @@
|
||||
package operation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/security"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type scriptedHTTPResponse struct {
|
||||
status int
|
||||
body string
|
||||
}
|
||||
|
||||
type scriptedHTTPClient struct {
|
||||
mu sync.Mutex
|
||||
responses map[string][]scriptedHTTPResponse
|
||||
calls []string
|
||||
}
|
||||
|
||||
func (c *scriptedHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
url := req.URL.String()
|
||||
c.calls = append(c.calls, url)
|
||||
|
||||
plans := c.responses[url]
|
||||
if len(plans) == 0 {
|
||||
return nil, fmt.Errorf("unexpected request to %s", url)
|
||||
}
|
||||
plan := plans[0]
|
||||
c.responses[url] = plans[1:]
|
||||
|
||||
return &http.Response{
|
||||
StatusCode: plan.status,
|
||||
Header: make(http.Header),
|
||||
Body: io.NopCloser(strings.NewReader(plan.body)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestShouldReassignUpload(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
err error
|
||||
want bool
|
||||
}{
|
||||
{name: "nil", err: nil, want: false},
|
||||
{name: "no response (transport)", err: &uploadStatusError{StatusCode: 0, err: fmt.Errorf("dial tcp: no such host")}, want: true},
|
||||
{name: "500 replica write failed", err: &uploadStatusError{StatusCode: 500, err: fmt.Errorf("failed to write to replicas")}, want: true},
|
||||
{name: "503 service unavailable", err: &uploadStatusError{StatusCode: 503, err: fmt.Errorf("busy")}, want: true},
|
||||
{name: "400 bad request", err: &uploadStatusError{StatusCode: 400, err: fmt.Errorf("bad needle")}, want: false},
|
||||
{name: "401 unauthorized", err: &uploadStatusError{StatusCode: 401, err: fmt.Errorf("wrong jwt")}, want: false},
|
||||
{name: "assign rpc transport", err: fmt.Errorf("assign volume: transport is closing"), want: true},
|
||||
{name: "other permanent", err: fmt.Errorf("mismatching cookie"), want: false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := ShouldReassignUpload(tc.err); got != tc.want {
|
||||
t.Fatalf("ShouldReassignUpload(%v) = %v, want %v", tc.err, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadWithRetryDataReassignsOnVolumeSizeExceeded(t *testing.T) {
|
||||
httpClient := &scriptedHTTPClient{
|
||||
responses: map[string][]scriptedHTTPResponse{
|
||||
"http://volume-a/1,first": {
|
||||
{status: http.StatusInternalServerError, body: `{"error":"failed to write to local disk: append to volume 1 size 0 actualSize 0: Volume Size 33555976 Exceeded 33554432"}`},
|
||||
{status: http.StatusInternalServerError, body: `{"error":"failed to write to local disk: append to volume 1 size 0 actualSize 0: Volume Size 33555976 Exceeded 33554432"}`},
|
||||
{status: http.StatusInternalServerError, body: `{"error":"failed to write to local disk: append to volume 1 size 0 actualSize 0: Volume Size 33555976 Exceeded 33554432"}`},
|
||||
},
|
||||
"http://volume-b/2,second": {
|
||||
{status: http.StatusCreated, body: `{"name":"test.bin","size":3}`},
|
||||
},
|
||||
},
|
||||
}
|
||||
uploader := newUploader(httpClient)
|
||||
|
||||
assignCalls := 0
|
||||
fileID, uploadResult, err := uploader.uploadWithRetryData(func() (string, string, security.EncodedJwt, error) {
|
||||
assignCalls++
|
||||
if assignCalls == 1 {
|
||||
return "1,first", "volume-a", "", nil
|
||||
}
|
||||
return "2,second", "volume-b", "", nil
|
||||
}, &UploadOption{Filename: "test.bin"}, []byte("abc"))
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expected success after reassignment, got %v", err)
|
||||
}
|
||||
if fileID != "2,second" {
|
||||
t.Fatalf("expected second file id, got %s", fileID)
|
||||
}
|
||||
if assignCalls != 2 {
|
||||
t.Fatalf("expected 2 assign attempts, got %d", assignCalls)
|
||||
}
|
||||
if uploadResult == nil || uploadResult.Name != "test.bin" {
|
||||
t.Fatalf("expected successful upload result, got %#v", uploadResult)
|
||||
}
|
||||
if len(httpClient.calls) != 4 {
|
||||
t.Fatalf("expected 4 upload attempts (3 same-url retries + 1 reassigned upload), got %d", len(httpClient.calls))
|
||||
}
|
||||
if httpClient.calls[0] != "http://volume-a/1,first" || httpClient.calls[3] != "http://volume-b/2,second" {
|
||||
t.Fatalf("unexpected upload call sequence: %#v", httpClient.calls)
|
||||
}
|
||||
}
|
||||
|
||||
// A volume whose replica peer is down reports "failed to write to replicas":
|
||||
// the primary write lands but replication fails. The client must re-assign to a
|
||||
// volume on live nodes and retry rather than losing the write.
|
||||
func TestUploadWithRetryDataReassignsOnReplicaWriteFailure(t *testing.T) {
|
||||
replicaErr := `{"error":"failed to write to replicas for volume 1: [volume-down:8080]: dial tcp: lookup volume-down: no such host"}`
|
||||
httpClient := &scriptedHTTPClient{
|
||||
responses: map[string][]scriptedHTTPResponse{
|
||||
"http://volume-a/1,first": {
|
||||
{status: http.StatusInternalServerError, body: replicaErr},
|
||||
{status: http.StatusInternalServerError, body: replicaErr},
|
||||
{status: http.StatusInternalServerError, body: replicaErr},
|
||||
},
|
||||
"http://volume-b/2,second": {
|
||||
{status: http.StatusCreated, body: `{"name":"test.bin","size":3}`},
|
||||
},
|
||||
},
|
||||
}
|
||||
uploader := newUploader(httpClient)
|
||||
|
||||
assignCalls := 0
|
||||
fileID, uploadResult, err := uploader.uploadWithRetryData(func() (string, string, security.EncodedJwt, error) {
|
||||
assignCalls++
|
||||
if assignCalls == 1 {
|
||||
return "1,first", "volume-a", "", nil
|
||||
}
|
||||
return "2,second", "volume-b", "", nil
|
||||
}, &UploadOption{Filename: "test.bin"}, []byte("abc"))
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("expected success after reassignment, got %v", err)
|
||||
}
|
||||
if fileID != "2,second" {
|
||||
t.Fatalf("expected reassigned file id, got %s", fileID)
|
||||
}
|
||||
if assignCalls != 2 {
|
||||
t.Fatalf("expected 2 assign attempts, got %d", assignCalls)
|
||||
}
|
||||
if uploadResult == nil || uploadResult.Name != "test.bin" {
|
||||
t.Fatalf("expected successful upload result, got %#v", uploadResult)
|
||||
}
|
||||
}
|
||||
|
||||
// bodyCapturingHTTPClient drains req.Body on every Do, optionally failing the
|
||||
// first attempt with a transport error so we can verify upload_content rewinds
|
||||
// the body before retrying.
|
||||
type bodyCapturingHTTPClient struct {
|
||||
mu sync.Mutex
|
||||
bodies [][]byte
|
||||
failFirst string
|
||||
successJSON string
|
||||
}
|
||||
|
||||
func (c *bodyCapturingHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
var captured []byte
|
||||
if req.Body != nil {
|
||||
b, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
captured = b
|
||||
}
|
||||
c.bodies = append(c.bodies, captured)
|
||||
|
||||
if len(c.bodies) == 1 && c.failFirst != "" {
|
||||
return nil, errors.New(c.failFirst)
|
||||
}
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusCreated,
|
||||
Header: make(http.Header),
|
||||
Body: io.NopCloser(strings.NewReader(c.successJSON)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// hangingAssignSeaweedClient is a SeaweedFilerClient whose AssignVolume blocks
|
||||
// until its context is done, so a test can prove UploadWithRetry bounds the RPC.
|
||||
type hangingAssignSeaweedClient struct {
|
||||
filer_pb.SeaweedFilerClient
|
||||
sawDeadline chan bool
|
||||
}
|
||||
|
||||
func (c *hangingAssignSeaweedClient) AssignVolume(ctx context.Context, in *filer_pb.AssignVolumeRequest, opts ...grpc.CallOption) (*filer_pb.AssignVolumeResponse, error) {
|
||||
_, ok := ctx.Deadline()
|
||||
// Non-blocking so a retry that calls this more than once can't wedge here.
|
||||
select {
|
||||
case c.sawDeadline <- ok:
|
||||
default:
|
||||
}
|
||||
<-ctx.Done() // simulate an overwhelmed filer that never answers
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
type hangingAssignFilerClient struct {
|
||||
inner *hangingAssignSeaweedClient
|
||||
}
|
||||
|
||||
func (c *hangingAssignFilerClient) WithFilerClient(streamingMode bool, fn func(filer_pb.SeaweedFilerClient) error) error {
|
||||
return fn(c.inner)
|
||||
}
|
||||
func (c *hangingAssignFilerClient) AdjustedUrl(loc *filer_pb.Location) string { return loc.GetUrl() }
|
||||
func (c *hangingAssignFilerClient) GetDataCenter() string { return "" }
|
||||
|
||||
// TestUploadWithRetryBoundsAssignVolume covers the case where an AssignVolume
|
||||
// against an overwhelmed filer carried no deadline, so the upload (and the FUSE
|
||||
// flush driving it) blocked forever. UploadWithRetry must give the RPC a
|
||||
// deadline and return once it expires instead of hanging.
|
||||
func TestUploadWithRetryBoundsAssignVolume(t *testing.T) {
|
||||
original := assignVolumeTimeout
|
||||
assignVolumeTimeout = 200 * time.Millisecond
|
||||
t.Cleanup(func() { assignVolumeTimeout = original })
|
||||
|
||||
client := &hangingAssignFilerClient{inner: &hangingAssignSeaweedClient{sawDeadline: make(chan bool, 1)}}
|
||||
uploader := newUploader(&scriptedHTTPClient{responses: map[string][]scriptedHTTPResponse{}})
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
_, _, err, _ := uploader.UploadWithRetry(client,
|
||||
&filer_pb.AssignVolumeRequest{Count: 1},
|
||||
&UploadOption{Filename: "test.bin"},
|
||||
bytes.NewReader([]byte("abc")),
|
||||
)
|
||||
done <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case sawDeadline := <-client.inner.sawDeadline:
|
||||
if !sawDeadline {
|
||||
t.Fatal("AssignVolume received a context with no deadline")
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("AssignVolume was never called")
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
if err == nil {
|
||||
t.Fatal("expected UploadWithRetry to fail once the assign deadline expired")
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("UploadWithRetry hung well past the assign timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUploadRewindsBodyOnConnectionReset reproduces issue #9139 follow-up:
|
||||
// when the inner Do retry fires on a "connection reset" / "closed network"
|
||||
// error, the *bytes.Reader body has already been consumed, so without an
|
||||
// explicit rewind the second attempt sends 0 bytes and Go's transport surfaces
|
||||
// "ContentLength=N with Body length 0".
|
||||
func TestUploadRewindsBodyOnConnectionReset(t *testing.T) {
|
||||
for _, transient := range []string{
|
||||
"connection reset by peer",
|
||||
"use of closed network connection",
|
||||
} {
|
||||
t.Run(transient, func(t *testing.T) {
|
||||
client := &bodyCapturingHTTPClient{
|
||||
failFirst: transient,
|
||||
successJSON: `{"name":"test.bin","size":42}`,
|
||||
}
|
||||
uploader := newUploader(client)
|
||||
|
||||
payload := bytes.Repeat([]byte("payload-"), 256) // 2048 bytes
|
||||
_, err := uploader.UploadData(context.Background(), payload, &UploadOption{
|
||||
UploadUrl: "http://volume/1,abc",
|
||||
Filename: "test.bin",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("upload should succeed after inner retry, got %v", err)
|
||||
}
|
||||
if len(client.bodies) != 2 {
|
||||
t.Fatalf("expected 2 Do attempts, got %d", len(client.bodies))
|
||||
}
|
||||
if len(client.bodies[0]) == 0 {
|
||||
t.Fatalf("first attempt sent an empty body; test setup wrong")
|
||||
}
|
||||
if !bytes.Equal(client.bodies[0], client.bodies[1]) {
|
||||
t.Fatalf("retry body length=%d differs from first attempt length=%d (body was not rewound)",
|
||||
len(client.bodies[1]), len(client.bodies[0]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplicationUploadPreservesUncompressedNeedle(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
payload []byte
|
||||
}{
|
||||
{
|
||||
name: "unknown binary sampling",
|
||||
payload: bytes.Repeat([]byte{0}, 32*1024),
|
||||
},
|
||||
{
|
||||
name: "receiver MIME detection",
|
||||
payload: bytes.Repeat([]byte("plain text content\n"), 2048),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var replicatedNeedle *needle.Needle
|
||||
var parseErr error
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
replicatedNeedle, _, _, parseErr = needle.CreateNeedleFromRequest(r, false, 1024*1024, &bytes.Buffer{})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = io.WriteString(w, `{"name":"payload.custom","size":32768}`)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
uploader := newUploader(server.Client())
|
||||
_, err := uploader.UploadData(context.Background(), tc.payload, &UploadOption{
|
||||
UploadUrl: server.URL + "/3,01637037d6?type=replicate",
|
||||
Filename: "payload.custom",
|
||||
IsReplication: true,
|
||||
MaxAttempts: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("replication upload failed: %v", err)
|
||||
}
|
||||
if parseErr != nil {
|
||||
t.Fatalf("parse replicated upload: %v", parseErr)
|
||||
}
|
||||
if replicatedNeedle == nil {
|
||||
t.Fatal("replica did not receive a needle")
|
||||
}
|
||||
if replicatedNeedle.IsCompressed() {
|
||||
t.Fatal("replication changed an uncompressed needle to compressed")
|
||||
}
|
||||
if !bytes.Equal(replicatedNeedle.Data, tc.payload) {
|
||||
t.Fatalf("replicated data differs: got %d bytes, want %d", len(replicatedNeedle.Data), len(tc.payload))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// deadReplicaClient simulates an unreachable replica: every request blocks for
|
||||
// dialDelay (standing in for the TCP dial timeout) and then fails, unless the
|
||||
// request context is cancelled first.
|
||||
type deadReplicaClient struct {
|
||||
mu sync.Mutex
|
||||
calls int
|
||||
dialDelay time.Duration
|
||||
}
|
||||
|
||||
func (c *deadReplicaClient) attempts() int {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.calls
|
||||
}
|
||||
|
||||
func (c *deadReplicaClient) Do(req *http.Request) (*http.Response, error) {
|
||||
c.mu.Lock()
|
||||
c.calls++
|
||||
c.mu.Unlock()
|
||||
select {
|
||||
case <-time.After(c.dialDelay):
|
||||
return nil, fmt.Errorf("dial tcp %s: i/o timeout", req.URL.Host)
|
||||
case <-req.Context().Done():
|
||||
return nil, req.Context().Err()
|
||||
}
|
||||
}
|
||||
|
||||
// TestUploadToDeadReplicaRetriesThreeTimes reproduces the dead-replica upload
|
||||
// stall: a synchronous replica write to an unreachable host pays the dial
|
||||
// timeout three times over, so one dead replica stalls the caller for ~3x the
|
||||
// dial timeout. In production dialDelay is the 10s dialer timeout, so the
|
||||
// caller blocks ~30s before the failure surfaces.
|
||||
func TestUploadToDeadReplicaRetriesThreeTimes(t *testing.T) {
|
||||
client := &deadReplicaClient{dialDelay: 100 * time.Millisecond}
|
||||
uploader := newUploader(client)
|
||||
|
||||
start := time.Now()
|
||||
_, err := uploader.UploadData(context.Background(), []byte("hello"), &UploadOption{
|
||||
UploadUrl: "http://dead-replica:8080/3,01",
|
||||
Filename: "test.bin",
|
||||
})
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected an error uploading to a dead replica")
|
||||
}
|
||||
if got := client.attempts(); got != 3 {
|
||||
t.Fatalf("dial attempts = %d, want 3 (each attempt pays the full dial timeout)", got)
|
||||
}
|
||||
t.Logf("dead replica stalled the caller for %v across %d attempts", elapsed, client.attempts())
|
||||
}
|
||||
|
||||
// TestUploadToDeadReplicaSingleAttempt verifies the fix: a synchronous replica
|
||||
// write makes a single attempt (MaxAttempts=1), so a dead replica fails after
|
||||
// one dial timeout instead of three. The outer client write still retries.
|
||||
func TestUploadToDeadReplicaSingleAttempt(t *testing.T) {
|
||||
client := &deadReplicaClient{dialDelay: 100 * time.Millisecond}
|
||||
uploader := newUploader(client)
|
||||
|
||||
_, err := uploader.UploadData(context.Background(), []byte("hello"), &UploadOption{
|
||||
UploadUrl: "http://dead-replica:8080/3,01",
|
||||
Filename: "test.bin",
|
||||
MaxAttempts: 1,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error uploading to a dead replica")
|
||||
}
|
||||
if got := client.attempts(); got != 1 {
|
||||
t.Fatalf("dial attempts = %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUploadRetryStopsOnContextCancel verifies the retry loop honors context
|
||||
// cancellation so DistributedOperation can abandon a slow replica once the
|
||||
// write outcome is already decided by another replica.
|
||||
func TestUploadRetryStopsOnContextCancel(t *testing.T) {
|
||||
client := &deadReplicaClient{dialDelay: time.Hour} // would block ~forever per attempt
|
||||
uploader := newUploader(client)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // already decided elsewhere
|
||||
|
||||
start := time.Now()
|
||||
_, err := uploader.UploadData(ctx, []byte("hello"), &UploadOption{
|
||||
UploadUrl: "http://dead-replica:8080/3,01",
|
||||
Filename: "test.bin",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected a context error")
|
||||
}
|
||||
if elapsed := time.Since(start); elapsed > 10*time.Second {
|
||||
t.Fatalf("upload did not abort on cancellation: took %v", elapsed)
|
||||
}
|
||||
if got := client.attempts(); got > 1 {
|
||||
t.Fatalf("dial attempts = %d, want at most 1 after cancellation", got)
|
||||
}
|
||||
}
|
||||
|
||||
// A ciphered upload hands the volume server ciphertext, so it cannot echo a
|
||||
// Content-MD5 back; without the caller's plaintext digest the chunk lands with
|
||||
// no ETag and every ETag computed from those chunks comes out empty
|
||||
// (issue #10968).
|
||||
func TestCipherUploadKeepsPlaintextETag(t *testing.T) {
|
||||
payload := bytes.Repeat([]byte("encrypt me\n"), 4096)
|
||||
digest := md5.Sum(payload)
|
||||
wantMd5 := base64.StdEncoding.EncodeToString(digest[:])
|
||||
|
||||
var storedNeedle *needle.Needle
|
||||
var sentContentMd5 string
|
||||
var parseErr error
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sentContentMd5 = r.Header.Get("Content-MD5")
|
||||
storedNeedle, _, _, parseErr = needle.CreateNeedleFromRequest(r, false, 1024*1024, &bytes.Buffer{})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, _ = io.WriteString(w, `{"name":"payload","size":45056}`)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
uploader := newUploader(server.Client())
|
||||
result, err := uploader.UploadData(context.Background(), payload, &UploadOption{
|
||||
UploadUrl: server.URL + "/3,01637037d6",
|
||||
Cipher: true,
|
||||
Md5: wantMd5,
|
||||
MaxAttempts: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("cipher upload failed: %v", err)
|
||||
}
|
||||
if parseErr != nil {
|
||||
t.Fatalf("parse ciphered upload: %v", parseErr)
|
||||
}
|
||||
if sentContentMd5 != "" {
|
||||
t.Fatalf("Content-MD5 %q sent to the volume server, which only sees ciphertext", sentContentMd5)
|
||||
}
|
||||
if result.ContentMd5 != wantMd5 {
|
||||
t.Fatalf("chunk ETag = %q, want the plaintext digest %q", result.ContentMd5, wantMd5)
|
||||
}
|
||||
if bytes.Equal(storedNeedle.Data, payload) {
|
||||
t.Fatal("volume server stored plaintext for a ciphered upload")
|
||||
}
|
||||
decrypted, err := util.Decrypt(storedNeedle.Data, util.CipherKey(result.CipherKey))
|
||||
if err != nil {
|
||||
t.Fatalf("decrypt stored needle: %v", err)
|
||||
}
|
||||
if result.Gzip > 0 {
|
||||
if decrypted, err = util.DecompressData(decrypted); err != nil {
|
||||
t.Fatalf("decompress stored needle: %v", err)
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(decrypted, payload) {
|
||||
t.Fatalf("decrypted %d bytes, want %d", len(decrypted), len(payload))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user