Files
seaweedfs/weed/storage/volume_destroy_ec_vif_test.go
T
Chris Lu 3674f9d04d fix(storage): keep EC .vif when deleting a coexisting regular volume (#9723)
* fix(storage): keep EC .vif when deleting a coexisting regular volume

A regular volume and an EC volume for the same id share <base>.vif. When
EC shards are distributed onto a server that still holds the regular
volume — the encode source, or any replica the planner targets — the
post-encode VolumeDelete ran removeVolumeFiles and stripped the shared
.vif, leaving the freshly built EC volume without its info file.

Skip the .vif in removeVolumeFiles when an EC volume for the same id
exists on the disk (mounted, or a sealed .ecx on disk). The regular
volume's .dat/.idx still go; the EC sidecars survive.

A two-server end-to-end test encodes a volume whose source and a stub
replica both also receive shards, and asserts the final on-disk layout:
both .dat/.idx gone, each server holding only its assigned shards plus
.ecx/.vif. Storage unit tests cover the with-EC and no-EC cases, and the
Rust seaweed-volume port carries the same guard and tests.

* test(storage): assert .idx is removed in the no-EC destroy case

Strengthen TestDestroyRemovesVifWhenNoEc to confirm the full regular
volume cleanup (.dat, .idx, .vif) when no EC volume coexists.
2026-05-28 15:39:31 -07:00

76 lines
2.8 KiB
Go

package storage
import (
"os"
"testing"
"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
"github.com/seaweedfs/seaweedfs/weed/storage/super_block"
"github.com/seaweedfs/seaweedfs/weed/storage/types"
"github.com/seaweedfs/seaweedfs/weed/util"
"github.com/stretchr/testify/require"
)
// A regular volume and an EC volume for the same id share <base>.vif. Deleting
// the regular volume must drop its .dat/.idx but keep the .vif so the
// coexisting EC volume's info file survives. This is the same-disk case that
// arises when EC shards are distributed onto a source/replica server before
// the original volume is deleted.
func TestDestroyKeepsVifWhenEcCoexists(t *testing.T) {
dir := t.TempDir()
v, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, &super_block.ReplicaPlacement{}, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
require.NoError(t, err)
v.location = newTestDiskLocation(dir)
_, _, _, err = v.writeNeedle2(newRandomNeedle(1), true, false)
require.NoError(t, err)
base := VolumeFileName(dir, "", 1)
vifPath := base + ".vif"
require.NoError(t, os.WriteFile(vifPath, []byte("ec-volume-info"), 0o644))
// An on-disk .ecx marks a coexisting EC volume for the same id.
ecxPath := erasure_coding.EcShardFileName("", dir, 1) + ".ecx"
require.NoError(t, os.WriteFile(ecxPath, []byte("ec-index"), 0o644))
require.NoError(t, v.Destroy(false, false))
assertFileExist(t, false, base+".dat")
assertFileExist(t, false, base+".idx")
assertFileExist(t, true, vifPath) // shared with the EC volume, must survive
assertFileExist(t, true, ecxPath) // EC sidecars are never touched here
}
// With no coexisting EC volume the .vif is a plain regular-volume file and is
// removed with the rest.
func TestDestroyRemovesVifWhenNoEc(t *testing.T) {
dir := t.TempDir()
v, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, &super_block.ReplicaPlacement{}, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
require.NoError(t, err)
v.location = newTestDiskLocation(dir)
_, _, _, err = v.writeNeedle2(newRandomNeedle(1), true, false)
require.NoError(t, err)
base := VolumeFileName(dir, "", 1)
vifPath := base + ".vif"
require.NoError(t, os.WriteFile(vifPath, []byte("regular-volume-info"), 0o644))
require.NoError(t, v.Destroy(false, false))
assertFileExist(t, false, base+".dat")
assertFileExist(t, false, base+".idx")
assertFileExist(t, false, vifPath) // removed along with the regular volume
}
func newTestDiskLocation(dir string) *DiskLocation {
loc := &DiskLocation{
Directory: dir,
IdxDirectory: dir,
DiskType: types.HddType,
MaxVolumeCount: 100,
MinFreeSpace: util.MinFreeSpace{Type: util.AsPercent, Percent: 1, Raw: "1"},
}
loc.volumes = make(map[needle.VolumeId]*Volume)
loc.ecVolumes = make(map[needle.VolumeId]*erasure_coding.EcVolume)
return loc
}