mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
Maintenance mode exists to fence a volume server so it can be evacuated without taking new writes (#7977), but the gate added in #8115 also rejected the RPCs evacuation issues against the source: VolumeMarkReadonly (the first step of every move, and the failure reported in #11066), VolumeDelete (the last step), and VolumeEcShardsDelete (the last step for EC shards). volumeServer.evacuate, volume.move and ec.balance therefore all failed on exactly the server they were meant to drain. Those three RPCs only remove data or restrict the server further, the same class as DeleteCollection and the unmount RPCs that were never gated, so they are exempted from the maintenance check in both the Go and Rust volume servers. Everything that adds data or reopens the server for writes (AllocateVolume, WriteNeedleBlob, BatchDelete, VolumeCopy, ReceiveFile, EC generate/copy/rebuild, vacuum, tiering, VolumeMarkWritable) stays blocked. A side effect is that scrub can now fence broken volumes readonly on a server already in maintenance. Fixes #11066 Generated with [Devin](https://devin.ai) Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
112 lines
4.5 KiB
Go
112 lines
4.5 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// newMaintenanceModeServer builds a volume server whose store holds volume vid
|
|
// in collection and has maintenance mode switched on.
|
|
func newMaintenanceModeServer(t *testing.T, vid needle.VolumeId, collection string) (*VolumeServer, string) {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
store := newTraversalTestStore(dir)
|
|
t.Cleanup(store.Close)
|
|
require.NoError(t, store.AddVolume(vid, collection, storage.NeedleMapInMemory, "000", "", 0, needle.GetCurrentVersion(), 0, types.HardDriveType, 0))
|
|
require.NoError(t, store.State.Update(&volume_server_pb.VolumeServerState{Maintenance: true}))
|
|
|
|
vs := &VolumeServer{
|
|
store: store,
|
|
grpcDialOption: grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
}
|
|
require.True(t, vs.MaintenanceMode())
|
|
return vs, dir
|
|
}
|
|
|
|
type fakeReadonlyAcceptingMaster struct {
|
|
master_pb.UnimplementedSeaweedServer
|
|
}
|
|
|
|
func (s *fakeReadonlyAcceptingMaster) VolumeMarkReadonly(context.Context, *master_pb.VolumeMarkReadonlyRequest) (*master_pb.VolumeMarkReadonlyResponse, error) {
|
|
return &master_pb.VolumeMarkReadonlyResponse{}, nil
|
|
}
|
|
|
|
// Evacuating a server in maintenance mode marks each volume readonly on the
|
|
// source, copies it, then deletes the source (issue #11066). Those source-side
|
|
// RPCs remove data or restrict the server further, so maintenance mode must
|
|
// let them through — otherwise the mode defeats the evacuation it exists for.
|
|
func TestMaintenanceModeAllowsVolumeMarkReadonly(t *testing.T) {
|
|
vid := needle.VolumeId(1)
|
|
vs, _ := newMaintenanceModeServer(t, vid, "")
|
|
vs.setCurrentMaster(startFakeMasterServerForLeaderLookup(t, &fakeReadonlyAcceptingMaster{}))
|
|
|
|
_, err := vs.VolumeMarkReadonly(context.Background(), &volume_server_pb.VolumeMarkReadonlyRequest{VolumeId: uint32(vid)})
|
|
require.NoError(t, err)
|
|
assert.True(t, vs.store.GetVolume(vid).IsReadOnly())
|
|
}
|
|
|
|
func TestMaintenanceModeAllowsVolumeDelete(t *testing.T) {
|
|
vid := needle.VolumeId(2)
|
|
vs, _ := newMaintenanceModeServer(t, vid, "")
|
|
|
|
_, err := vs.VolumeDelete(context.Background(), &volume_server_pb.VolumeDeleteRequest{VolumeId: uint32(vid)})
|
|
require.NoError(t, err)
|
|
assert.Nil(t, vs.store.GetVolume(vid), "volume should be gone from the store")
|
|
}
|
|
|
|
func TestMaintenanceModeAllowsVolumeEcShardsDelete(t *testing.T) {
|
|
const collection = "ec-maint"
|
|
vid := needle.VolumeId(3)
|
|
vs, dir := newMaintenanceModeServer(t, needle.VolumeId(99), "")
|
|
|
|
base := erasure_coding.EcShardFileName(collection, dir, int(vid))
|
|
require.NoError(t, os.WriteFile(base+".ecx", make([]byte, 16), 0o644))
|
|
for _, id := range []int{0, 1} {
|
|
require.NoError(t, os.WriteFile(base+erasure_coding.ToExt(id), []byte("s"), 0o644))
|
|
}
|
|
|
|
_, err := vs.VolumeEcShardsDelete(context.Background(), &volume_server_pb.VolumeEcShardsDeleteRequest{
|
|
VolumeId: uint32(vid),
|
|
Collection: collection,
|
|
ShardIds: []uint32{0, 1},
|
|
})
|
|
require.NoError(t, err)
|
|
assert.False(t, util.FileExists(base+erasure_coding.ToExt(0)))
|
|
assert.False(t, util.FileExists(base+erasure_coding.ToExt(1)))
|
|
}
|
|
|
|
// Maintenance mode keeps rejecting RPCs that add data to the server or reopen
|
|
// it for writes; only the removal/restriction path above is exempt.
|
|
func TestMaintenanceModeStillBlocksWrites(t *testing.T) {
|
|
vid := needle.VolumeId(4)
|
|
vs, _ := newMaintenanceModeServer(t, vid, "")
|
|
wantErr := vs.CheckMaintenanceMode().Error()
|
|
ctx := context.Background()
|
|
|
|
_, err := vs.AllocateVolume(ctx, &volume_server_pb.AllocateVolumeRequest{VolumeId: 5, Replication: "000", DiskType: string(types.HardDriveType)})
|
|
assert.EqualError(t, err, wantErr, "AllocateVolume")
|
|
|
|
_, err = vs.WriteNeedleBlob(ctx, &volume_server_pb.WriteNeedleBlobRequest{VolumeId: uint32(vid), NeedleId: 1, Size: 1, NeedleBlob: []byte{0}})
|
|
assert.EqualError(t, err, wantErr, "WriteNeedleBlob")
|
|
|
|
_, err = vs.BatchDelete(ctx, &volume_server_pb.BatchDeleteRequest{FileIds: []string{"4,01637037d6"}})
|
|
assert.EqualError(t, err, wantErr, "BatchDelete")
|
|
|
|
_, err = vs.VolumeMarkWritable(ctx, &volume_server_pb.VolumeMarkWritableRequest{VolumeId: uint32(vid)})
|
|
assert.EqualError(t, err, wantErr, "VolumeMarkWritable")
|
|
}
|