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
385 lines
13 KiB
Go
385 lines
13 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/stats"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/backend"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
const tierTestBackendName = "tier_test_local_dir.default"
|
|
|
|
// tierTestBackend is a fake BackendStorage backed by files in a temp dir. It
|
|
// records DeleteFile calls so a test can assert that a shared remote object is
|
|
// (or is not) removed.
|
|
type tierTestBackend struct {
|
|
root string
|
|
|
|
mu sync.Mutex
|
|
deletes []string
|
|
|
|
lastUploadConcurrency int
|
|
lastDownloadConcurrency int
|
|
}
|
|
|
|
func (b *tierTestBackend) ToProperties() map[string]string { return map[string]string{"root": b.root} }
|
|
|
|
func (b *tierTestBackend) NewStorageFile(key string, tierInfo *volume_server_pb.VolumeInfo) backend.BackendStorageFile {
|
|
return &tierTestBackendFile{backend: b, key: key, tierInfo: tierInfo}
|
|
}
|
|
|
|
func (b *tierTestBackend) CopyFile(f *os.File, fn func(progressed int64, percentage float32) error, concurrency int) (key string, size int64, err error) {
|
|
b.mu.Lock()
|
|
b.lastUploadConcurrency = concurrency
|
|
b.mu.Unlock()
|
|
key = fmt.Sprintf("obj-%d", time.Now().UnixNano())
|
|
dst := filepath.Join(b.root, key)
|
|
out, err := os.Create(dst)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
defer out.Close()
|
|
if _, err = f.Seek(0, io.SeekStart); err != nil {
|
|
return "", 0, err
|
|
}
|
|
written, err := io.Copy(out, f)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
return key, written, nil
|
|
}
|
|
|
|
func (b *tierTestBackend) DownloadFile(fileName string, key string, fn func(progressed int64, percentage float32) error, concurrency int) (size int64, err error) {
|
|
b.mu.Lock()
|
|
b.lastDownloadConcurrency = concurrency
|
|
b.mu.Unlock()
|
|
in, err := os.Open(filepath.Join(b.root, key))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer in.Close()
|
|
out, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
written, err := io.Copy(out, in)
|
|
if err != nil {
|
|
out.Close()
|
|
return 0, err
|
|
}
|
|
// mirror the real backends: fsync the .dat before close so the caller can
|
|
// trim the .vif and delete the remote object without risking a torn write.
|
|
if syncErr := out.Sync(); syncErr != nil {
|
|
out.Close()
|
|
return 0, syncErr
|
|
}
|
|
if closeErr := out.Close(); closeErr != nil {
|
|
return 0, closeErr
|
|
}
|
|
return written, nil
|
|
}
|
|
|
|
func (b *tierTestBackend) DeleteFile(key string) error {
|
|
b.mu.Lock()
|
|
b.deletes = append(b.deletes, key)
|
|
b.mu.Unlock()
|
|
return os.Remove(filepath.Join(b.root, key))
|
|
}
|
|
|
|
func (b *tierTestBackend) deleteHistory() []string {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
out := make([]string, len(b.deletes))
|
|
copy(out, b.deletes)
|
|
return out
|
|
}
|
|
|
|
func (b *tierTestBackend) objectExists(key string) bool {
|
|
_, err := os.Stat(filepath.Join(b.root, key))
|
|
return err == nil
|
|
}
|
|
|
|
func (b *tierTestBackend) concurrencySeen() (upload, download int) {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.lastUploadConcurrency, b.lastDownloadConcurrency
|
|
}
|
|
|
|
type tierTestBackendFile struct {
|
|
backend *tierTestBackend
|
|
key string
|
|
tierInfo *volume_server_pb.VolumeInfo
|
|
}
|
|
|
|
func (f *tierTestBackendFile) ReadAt(p []byte, off int64) (int, error) {
|
|
in, err := os.Open(filepath.Join(f.backend.root, f.key))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer in.Close()
|
|
return in.ReadAt(p, off)
|
|
}
|
|
func (f *tierTestBackendFile) WriteAt(p []byte, off int64) (int, error) { panic("not implemented") }
|
|
func (f *tierTestBackendFile) Truncate(off int64) error { panic("not implemented") }
|
|
func (f *tierTestBackendFile) Close() error { return nil }
|
|
func (f *tierTestBackendFile) Name() string { return f.key }
|
|
func (f *tierTestBackendFile) Sync() error { return nil }
|
|
func (f *tierTestBackendFile) GetStat() (int64, time.Time, error) {
|
|
files := f.tierInfo.GetFiles()
|
|
if len(files) == 0 {
|
|
return 0, time.Time{}, fmt.Errorf("remote file info not found")
|
|
}
|
|
return int64(files[0].FileSize), time.Unix(int64(files[0].ModifiedTime), 0), nil
|
|
}
|
|
|
|
// fakeTierStream is a no-op server stream for the tier-download RPC. The
|
|
// embedded grpc.ServerStream is nil, so Context is implemented here rather than
|
|
// promoted -- the RPC reads it to authorize the caller.
|
|
type fakeTierStream struct {
|
|
grpc.ServerStream
|
|
}
|
|
|
|
func (s *fakeTierStream) Send(*volume_server_pb.VolumeTierMoveDatFromRemoteResponse) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *fakeTierStream) Context() context.Context {
|
|
return context.Background()
|
|
}
|
|
|
|
func newTierTestStore(t *testing.T, dir string) *storage.Store {
|
|
t.Helper()
|
|
diskIOProbeConfig := stats.DefaultDiskIOProbeConfig()
|
|
store := storage.NewStore(nil, "localhost", 8080, 18080, "http://localhost:8080", "",
|
|
[]string{dir}, []int32{100}, []util.MinFreeSpace{{}}, "",
|
|
storage.NeedleMapInMemory, []types.DiskType{types.HardDriveType}, nil, 3, diskIOProbeConfig)
|
|
|
|
done := make(chan bool)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-store.NewVolumesChan:
|
|
case <-store.DeletedVolumesChan:
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
t.Cleanup(func() {
|
|
store.Close()
|
|
close(done)
|
|
})
|
|
return store
|
|
}
|
|
|
|
// tierUpVolumeOnDisk creates a real local volume in dir, writes a few needles,
|
|
// uploads the .dat to the fake backend, rewrites the .vif to remote mode, and
|
|
// removes the local .dat — mirroring volume_grpc_tier_upload.go. It returns the
|
|
// remote object key and the original local .dat bytes for later comparison.
|
|
func tierUpVolumeOnDisk(t *testing.T, dir string, vid needle.VolumeId, b *tierTestBackend) (key string, localDat []byte) {
|
|
t.Helper()
|
|
store := newTierTestStore(t, dir)
|
|
if err := store.AddVolume(vid, "", storage.NeedleMapInMemory, "000", "", 0, needle.GetCurrentVersion(), 0, types.HardDriveType, 0); err != nil {
|
|
t.Fatalf("add volume: %v", err)
|
|
}
|
|
for i := 1; i <= 5; i++ {
|
|
n := new(needle.Needle)
|
|
n.Id = types.Uint64ToNeedleId(uint64(i))
|
|
n.Data = []byte(fmt.Sprintf("payload-%d-localdisk", i))
|
|
n.Checksum = needle.NewCRC(n.Data)
|
|
if _, err := store.WriteVolumeNeedle(vid, n, true, false); err != nil {
|
|
t.Fatalf("write needle %d: %v", i, err)
|
|
}
|
|
}
|
|
|
|
v := store.GetVolume(vid)
|
|
if v == nil {
|
|
t.Fatal("volume not found after add")
|
|
}
|
|
diskFile, ok := v.DataBackend.(*backend.DiskFile)
|
|
if !ok {
|
|
t.Fatalf("expected on-disk backend before tier-up, got %T", v.DataBackend)
|
|
}
|
|
datPath := v.FileName(".dat")
|
|
key, size, err := b.CopyFile(diskFile.File, nil, 0)
|
|
if err != nil {
|
|
t.Fatalf("upload to fake backend: %v", err)
|
|
}
|
|
|
|
bType, bId := backend.BackendNameToTypeId(tierTestBackendName)
|
|
v.GetVolumeInfo().Files = append(v.GetVolumeInfo().GetFiles(), &volume_server_pb.RemoteFile{
|
|
BackendType: bType,
|
|
BackendId: bId,
|
|
Key: key,
|
|
FileSize: uint64(size),
|
|
ModifiedTime: uint64(time.Now().Unix()),
|
|
Extension: ".dat",
|
|
})
|
|
if err := v.SaveVolumeInfo(); err != nil {
|
|
t.Fatalf("save volume info: %v", err)
|
|
}
|
|
|
|
localDat, err = os.ReadFile(datPath)
|
|
if err != nil {
|
|
t.Fatalf("read local dat: %v", err)
|
|
}
|
|
if err := store.UnmountVolume(vid); err != nil {
|
|
t.Fatalf("unmount: %v", err)
|
|
}
|
|
if err := os.Remove(datPath); err != nil {
|
|
t.Fatalf("remove local dat: %v", err)
|
|
}
|
|
return key, localDat
|
|
}
|
|
|
|
// TestTierMoveDatFromRemote_KeepRemote_LeavesReplicaLocal reproduces the
|
|
// multi-replica tier-download data loss: when KeepRemoteDatFile=true (every
|
|
// replica except the last), the replica must end up served from its local .dat
|
|
// with the shared remote object intact. Before the fix the keep-remote path
|
|
// returned right after the download without trimming the .vif or swapping the
|
|
// data backend, so the replica stayed remote-backed and a subsequent delete of
|
|
// the shared object on the final replica bricked it.
|
|
func TestTierMoveDatFromRemote_KeepRemote_LeavesReplicaLocal(t *testing.T) {
|
|
b := &tierTestBackend{root: t.TempDir()}
|
|
backend.BackendStorages[tierTestBackendName] = b
|
|
t.Cleanup(func() { delete(backend.BackendStorages, tierTestBackendName) })
|
|
|
|
dir := t.TempDir()
|
|
const vid = needle.VolumeId(71)
|
|
key, localDat := tierUpVolumeOnDisk(t, dir, vid, b)
|
|
|
|
if !b.objectExists(key) {
|
|
t.Fatal("remote object missing after tier-up")
|
|
}
|
|
|
|
// Mount the tiered volume the way a volume server does at startup.
|
|
store := newTierTestStore(t, dir)
|
|
v := store.GetVolume(vid)
|
|
if v == nil {
|
|
t.Fatal("tiered volume not loaded by store")
|
|
}
|
|
if !v.HasRemoteFile() {
|
|
t.Fatal("volume should load in remote mode before download")
|
|
}
|
|
|
|
vs := &VolumeServer{store: store}
|
|
req := &volume_server_pb.VolumeTierMoveDatFromRemoteRequest{
|
|
VolumeId: uint32(vid),
|
|
Collection: "",
|
|
KeepRemoteDatFile: true,
|
|
}
|
|
if err := vs.VolumeTierMoveDatFromRemote(req, &fakeTierStream{}); err != nil {
|
|
t.Fatalf("VolumeTierMoveDatFromRemote: %v", err)
|
|
}
|
|
|
|
// keepRemote: the shared object must survive untouched.
|
|
if len(b.deleteHistory()) != 0 {
|
|
t.Fatalf("KeepRemoteDatFile=true must not delete the shared remote object, deletes: %v", b.deleteHistory())
|
|
}
|
|
if !b.objectExists(key) {
|
|
t.Fatal("remote object must still exist after a keep-remote download")
|
|
}
|
|
|
|
// The in-memory volume must now be served from a local DiskFile, not the
|
|
// remote backend — this is the assertion that fails on the pre-fix code.
|
|
if _, ok := v.DataBackend.(*backend.DiskFile); !ok {
|
|
t.Fatalf("after keep-remote download the data backend must be local DiskFile, got %T", v.DataBackend)
|
|
}
|
|
// The live volume must also report non-remote immediately, without waiting for
|
|
// a remount: a delete arriving before the remount would otherwise skip the .dat
|
|
// tombstone, and the phantom-.dat guard would stay disabled.
|
|
if v.HasRemoteFile() {
|
|
t.Fatal("after download the live volume must no longer be remote-backed")
|
|
}
|
|
|
|
// Remount and confirm the volume is no longer remote-backed and reads come
|
|
// from the local .dat (matching the bytes uploaded before tiering).
|
|
if err := store.UnmountVolume(vid); err != nil {
|
|
t.Fatalf("unmount after download: %v", err)
|
|
}
|
|
if err := store.MountVolume(vid, &req.Collection); err != nil {
|
|
t.Fatalf("remount after download: %v", err)
|
|
}
|
|
v2 := store.GetVolume(vid)
|
|
if v2 == nil {
|
|
t.Fatal("volume missing after remount")
|
|
}
|
|
if v2.HasRemoteFile() {
|
|
t.Fatal("after a keep-remote download the remounted replica must not be remote-backed")
|
|
}
|
|
if _, ok := v2.DataBackend.(*backend.DiskFile); !ok {
|
|
t.Fatalf("remounted replica must read from local DiskFile, got %T", v2.DataBackend)
|
|
}
|
|
|
|
// The .vif must no longer reference the remote object.
|
|
storageName, storageKey := v2.RemoteStorageNameKey()
|
|
if storageName != "" || storageKey != "" {
|
|
t.Fatalf("trimmed .vif must not reference remote object, got %q/%q", storageName, storageKey)
|
|
}
|
|
|
|
// The local .dat must hold the downloaded content (the original bytes).
|
|
gotDat, err := os.ReadFile(v2.FileName(".dat"))
|
|
if err != nil {
|
|
t.Fatalf("read local dat after download: %v", err)
|
|
}
|
|
if !bytes.Equal(gotDat, localDat) {
|
|
t.Fatalf("local .dat content mismatch: got %d bytes, want %d", len(gotDat), len(localDat))
|
|
}
|
|
}
|
|
|
|
// TestTierMoveConcurrencyPlumbing verifies the -concurrency value travels the
|
|
// whole tier-move path to CopyFile and DownloadFile.
|
|
func TestTierMoveConcurrencyPlumbing(t *testing.T) {
|
|
b := &tierTestBackend{root: t.TempDir()}
|
|
backend.BackendStorages[tierTestBackendName] = b
|
|
t.Cleanup(func() { delete(backend.BackendStorages, tierTestBackendName) })
|
|
|
|
dir := t.TempDir()
|
|
const vid = needle.VolumeId(72)
|
|
tierUpVolumeOnDisk(t, dir, vid, b)
|
|
|
|
// download path: request concurrency must reach DownloadFile
|
|
store := newTierTestStore(t, dir)
|
|
v := store.GetVolume(vid)
|
|
if v == nil {
|
|
t.Fatal("tiered volume not loaded by store")
|
|
}
|
|
vs := &VolumeServer{store: store}
|
|
if err := vs.VolumeTierMoveDatFromRemote(&volume_server_pb.VolumeTierMoveDatFromRemoteRequest{
|
|
VolumeId: uint32(vid),
|
|
Concurrency: 4,
|
|
}, &fakeTierStream{}); err != nil {
|
|
t.Fatalf("VolumeTierMoveDatFromRemote: %v", err)
|
|
}
|
|
if up, down := b.concurrencySeen(); down != 4 {
|
|
t.Fatalf("DownloadFile concurrency = %d (upload=%d), want 4", down, up)
|
|
}
|
|
|
|
// upload path: the volume is local again after the download
|
|
if err := vs.VolumeTierMoveDatToRemote(&volume_server_pb.VolumeTierMoveDatToRemoteRequest{
|
|
VolumeId: uint32(vid),
|
|
DestinationBackendName: tierTestBackendName,
|
|
Concurrency: 3,
|
|
}, &discardServerStream[volume_server_pb.VolumeTierMoveDatToRemoteResponse]{}); err != nil {
|
|
t.Fatalf("VolumeTierMoveDatToRemote: %v", err)
|
|
}
|
|
if up, down := b.concurrencySeen(); up != 3 || down != 4 {
|
|
t.Fatalf("CopyFile concurrency = %d (download=%d), want 3/4", up, down)
|
|
}
|
|
}
|