Files
seaweedfs/weed/server/volume_grpc_block.go
T
pingqiuandClaude Opus 4.6 55013e103b feat: Phase 20 Stage 0+1 closure — bootstrap + sustained workload on hardware
Stage 0 (bootstrap closure): PASS on m01/M02
  - create RF=2 sync_all → 10s shipper wait → 4k fsync → publish_healthy
  - Proves: BarrierAccepted observation, ShipperConnected, DurableLSN > 0

Stage 1 (sustained workload): 32/33 actions PASS
  - bootstrap → fio 10s randwrite → dd_write 1M×2 fsync → data checksum
  - Remaining: auto-failover promotion (separate issue)

Key fixes:
  - BarrierAccepted callback: SyncCache success → core DurableLSN update
  - BarrierRejected callback: barrier failures surface to core with reason
  - Shipper state callback for new volumes (not just startup volumes)
  - CatchUpTo ctrl conn reset: prevents stale control channel after recovery
  - CP13-6 max-bytes budget suspended: uses replicaFlushedLSN which can't
    advance without barrier; kills healthy shippers during async writes.
    Will be replaced by v2 negotiated sync/recovery protocol.
  - Barrier diagnostic logging: start/fail/success with reason and LSN
  - Scenario restructured: Stage 0 (bootstrap-closure) + Stage 1 (failover)
  - dd_write: sync_mode param + real stderr capture
  - sw-test-runner suite command: deploy once, run N scenarios
  - WAL size plumbing: proto + API + handler (forward-compatible)

Known: 6 blockvol/server test failures from Barrier() path change
(bounded catch-up in Barrier). Need test updates to match new semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 19:55:12 -07:00

208 lines
7.5 KiB
Go

package weed_server
import (
"context"
"fmt"
"strings"
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
)
// AllocateBlockVolume creates a new block volume on this volume server.
func (vs *VolumeServer) AllocateBlockVolume(_ context.Context, req *volume_server_pb.AllocateBlockVolumeRequest) (*volume_server_pb.AllocateBlockVolumeResponse, error) {
if vs.blockService == nil {
return nil, fmt.Errorf("block service not enabled on this volume server")
}
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
if req.SizeBytes == 0 {
return nil, fmt.Errorf("size_bytes must be > 0")
}
path, iqn, iscsiAddr, err := vs.blockService.CreateBlockVolWithOptions(req.Name, req.SizeBytes, req.WalSizeBytes, req.DiskType, req.DurabilityMode)
if err != nil {
return nil, fmt.Errorf("create block volume %q: %w", req.Name, err)
}
// R1-1: Return deterministic replication ports so master can wire WAL shipping.
dataPort, ctrlPort, rebuildPort := vs.blockService.ReplicationPorts(path)
host := vs.blockService.ListenAddr()
if idx := strings.LastIndex(host, ":"); idx >= 0 {
host = host[:idx]
}
// Canonicalize: if host is empty (VS bound to ":port"), resolve to routable IP.
if host == "" || host == "0.0.0.0" || host == "::" || host == "[::]" {
host = blockvol.PreferredOutboundIP()
}
resp := &volume_server_pb.AllocateBlockVolumeResponse{
Path: path,
Iqn: iqn,
IscsiAddr: iscsiAddr,
ReplicaDataAddr: fmt.Sprintf("%s:%d", host, dataPort),
ReplicaCtrlAddr: fmt.Sprintf("%s:%d", host, ctrlPort),
RebuildListenAddr: fmt.Sprintf("%s:%d", host, rebuildPort),
}
if nvmeAddr := vs.blockService.NvmeListenAddr(); nvmeAddr != "" {
resp.NvmeAddr = nvmeAddr
resp.Nqn = vs.blockService.NQN(req.Name)
}
return resp, nil
}
// VolumeServerDeleteBlockVolume deletes a block volume on this volume server.
func (vs *VolumeServer) VolumeServerDeleteBlockVolume(_ context.Context, req *volume_server_pb.VolumeServerDeleteBlockVolumeRequest) (*volume_server_pb.VolumeServerDeleteBlockVolumeResponse, error) {
if vs.blockService == nil {
return nil, fmt.Errorf("block service not enabled on this volume server")
}
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
if err := vs.blockService.DeleteBlockVol(req.Name); err != nil {
return nil, fmt.Errorf("delete block volume %q: %w", req.Name, err)
}
return &volume_server_pb.VolumeServerDeleteBlockVolumeResponse{}, nil
}
// SnapshotBlockVolume creates a snapshot on a block volume.
func (vs *VolumeServer) SnapshotBlockVolume(_ context.Context, req *volume_server_pb.SnapshotBlockVolumeRequest) (*volume_server_pb.SnapshotBlockVolumeResponse, error) {
if vs.blockService == nil {
return nil, fmt.Errorf("block service not enabled on this volume server")
}
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
createdAt, sizeBytes, err := vs.blockService.SnapshotBlockVol(req.Name, req.SnapshotId)
if err != nil {
return nil, fmt.Errorf("snapshot block volume %q: %w", req.Name, err)
}
return &volume_server_pb.SnapshotBlockVolumeResponse{
SnapshotId: req.SnapshotId,
CreatedAt: createdAt,
SizeBytes: sizeBytes,
}, nil
}
// DeleteBlockSnapshot deletes a snapshot from a block volume.
func (vs *VolumeServer) DeleteBlockSnapshot(_ context.Context, req *volume_server_pb.DeleteBlockSnapshotRequest) (*volume_server_pb.DeleteBlockSnapshotResponse, error) {
if vs.blockService == nil {
return nil, fmt.Errorf("block service not enabled on this volume server")
}
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
if err := vs.blockService.DeleteBlockSnapshot(req.Name, req.SnapshotId); err != nil {
return nil, fmt.Errorf("delete block snapshot %q/%d: %w", req.Name, req.SnapshotId, err)
}
return &volume_server_pb.DeleteBlockSnapshotResponse{}, nil
}
// ListBlockSnapshots lists all snapshots on a block volume.
func (vs *VolumeServer) ListBlockSnapshots(_ context.Context, req *volume_server_pb.ListBlockSnapshotsRequest) (*volume_server_pb.ListBlockSnapshotsResponse, error) {
if vs.blockService == nil {
return nil, fmt.Errorf("block service not enabled on this volume server")
}
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
infos, volSize, err := vs.blockService.ListBlockSnapshots(req.Name)
if err != nil {
return nil, fmt.Errorf("list block snapshots %q: %w", req.Name, err)
}
resp := &volume_server_pb.ListBlockSnapshotsResponse{}
for _, s := range infos {
resp.Snapshots = append(resp.Snapshots, &volume_server_pb.BlockSnapshotInfo{
SnapshotId: s.ID,
CreatedAt: s.CreatedAt.Unix(),
VolumeSizeBytes: volSize,
})
}
return resp, nil
}
// PrepareExpandBlockVolume prepares a block volume for expansion without committing.
func (vs *VolumeServer) PrepareExpandBlockVolume(_ context.Context, req *volume_server_pb.PrepareExpandBlockVolumeRequest) (*volume_server_pb.PrepareExpandBlockVolumeResponse, error) {
if vs.blockService == nil {
return nil, fmt.Errorf("block service not enabled on this volume server")
}
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
if req.NewSizeBytes == 0 {
return nil, fmt.Errorf("new_size_bytes must be > 0")
}
if err := vs.blockService.PrepareExpandBlockVol(req.Name, req.NewSizeBytes, req.ExpandEpoch); err != nil {
return nil, fmt.Errorf("prepare expand block volume %q: %w", req.Name, err)
}
return &volume_server_pb.PrepareExpandBlockVolumeResponse{}, nil
}
// CommitExpandBlockVolume commits a prepared block volume expansion.
func (vs *VolumeServer) CommitExpandBlockVolume(_ context.Context, req *volume_server_pb.CommitExpandBlockVolumeRequest) (*volume_server_pb.CommitExpandBlockVolumeResponse, error) {
if vs.blockService == nil {
return nil, fmt.Errorf("block service not enabled on this volume server")
}
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
capacity, err := vs.blockService.CommitExpandBlockVol(req.Name, req.ExpandEpoch)
if err != nil {
return nil, fmt.Errorf("commit expand block volume %q: %w", req.Name, err)
}
return &volume_server_pb.CommitExpandBlockVolumeResponse{
CapacityBytes: capacity,
}, nil
}
// CancelExpandBlockVolume cancels a prepared block volume expansion.
func (vs *VolumeServer) CancelExpandBlockVolume(_ context.Context, req *volume_server_pb.CancelExpandBlockVolumeRequest) (*volume_server_pb.CancelExpandBlockVolumeResponse, error) {
if vs.blockService == nil {
return nil, fmt.Errorf("block service not enabled on this volume server")
}
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
if err := vs.blockService.CancelExpandBlockVol(req.Name, req.ExpandEpoch); err != nil {
return nil, fmt.Errorf("cancel expand block volume %q: %w", req.Name, err)
}
return &volume_server_pb.CancelExpandBlockVolumeResponse{}, nil
}
// ExpandBlockVolume expands a block volume to a new size.
func (vs *VolumeServer) ExpandBlockVolume(_ context.Context, req *volume_server_pb.ExpandBlockVolumeRequest) (*volume_server_pb.ExpandBlockVolumeResponse, error) {
if vs.blockService == nil {
return nil, fmt.Errorf("block service not enabled on this volume server")
}
if req.Name == "" {
return nil, fmt.Errorf("name is required")
}
if req.NewSizeBytes == 0 {
return nil, fmt.Errorf("new_size_bytes must be > 0")
}
actualSize, err := vs.blockService.ExpandBlockVol(req.Name, req.NewSizeBytes)
if err != nil {
return nil, fmt.Errorf("expand block volume %q: %w", req.Name, err)
}
return &volume_server_pb.ExpandBlockVolumeResponse{
CapacityBytes: actualSize,
}, nil
}