mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
Phase 3 delivers five checkpoints: CP1 Engine Tuning: BlockVolConfig tunables, 256-shard DirtyMap, adaptive group commit (low-watermark immediate flush), WAL pressure handling with backpressure and ErrWALFull timeout. CP2 iSCSI Session Refactor: RX/TX goroutine split with respCh (cap 64), txLoop for serialized response writes, StatSN assignment modes. Login phase stays single-goroutine; full-duplex after login. CP3 Store Integration: BlockVolAdapter (iscsi.BlockDevice interface), BlockVolumeStore management, BlockService in volume_server_block.go, CLI flags (--block.listen/dir/iqn.prefix), sw-block-attach.sh helper. CP5 Concurrency Hardening: WAL reuse guard (LSN validation in ReadLBA), opsOutstanding counter with beginOp/endOp + Close drain, appendWithRetry shared by WriteLBA and TrimLBA, flusher LSN guard in FlushOnce. Bug fixes (P3-BUG-2–11): unbounded pending queue cap, Data-Out timeout, flusher error logging, GroupCommitter panic recovery, Close vs concurrent ops guard, target shutdown race, WAL-full retry vs Close, WRITE SAME(16) for XFS, MODE SENSE(10) + VPD 0xB0/0xB2 for Linux kernel compatibility. 797 tests passing (517 engine + 280 iSCSI), go vet clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
115 lines
3.2 KiB
Go
115 lines
3.2 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/iscsi"
|
|
)
|
|
|
|
// BlockService manages block volumes and the iSCSI target server.
|
|
type BlockService struct {
|
|
blockStore *storage.BlockVolumeStore
|
|
targetServer *iscsi.TargetServer
|
|
iqnPrefix string
|
|
}
|
|
|
|
// StartBlockService scans blockDir for .blk files, opens them as block volumes,
|
|
// registers them with an iSCSI target server, and starts listening.
|
|
// Returns nil if blockDir is empty (feature disabled).
|
|
func StartBlockService(listenAddr, blockDir, iqnPrefix string) *BlockService {
|
|
if blockDir == "" {
|
|
return nil
|
|
}
|
|
|
|
if iqnPrefix == "" {
|
|
iqnPrefix = "iqn.2024-01.com.seaweedfs:vol."
|
|
}
|
|
|
|
bs := &BlockService{
|
|
blockStore: storage.NewBlockVolumeStore(),
|
|
iqnPrefix: iqnPrefix,
|
|
}
|
|
|
|
logger := log.New(os.Stderr, "iscsi: ", log.LstdFlags)
|
|
|
|
config := iscsi.DefaultTargetConfig()
|
|
config.TargetName = iqnPrefix + "default"
|
|
|
|
bs.targetServer = iscsi.NewTargetServer(listenAddr, config, logger)
|
|
|
|
// Scan blockDir for .blk files.
|
|
entries, err := os.ReadDir(blockDir)
|
|
if err != nil {
|
|
glog.Warningf("block service: cannot read dir %s: %v", blockDir, err)
|
|
return bs
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".blk") {
|
|
continue
|
|
}
|
|
path := filepath.Join(blockDir, entry.Name())
|
|
vol, err := bs.blockStore.AddBlockVolume(path)
|
|
if err != nil {
|
|
// Auto-initialize raw files (e.g. created via truncate).
|
|
info, serr := entry.Info()
|
|
if serr == nil && info.Size() > 0 {
|
|
glog.V(0).Infof("block service: auto-creating blockvol %s (%d bytes)", path, info.Size())
|
|
os.Remove(path) // remove raw file so CreateBlockVol can use O_EXCL
|
|
created, cerr := blockvol.CreateBlockVol(path, blockvol.CreateOptions{
|
|
VolumeSize: uint64(info.Size()),
|
|
})
|
|
if cerr != nil {
|
|
glog.Warningf("block service: auto-create %s: %v", path, cerr)
|
|
continue
|
|
}
|
|
created.Close()
|
|
vol, err = bs.blockStore.AddBlockVolume(path)
|
|
if err != nil {
|
|
glog.Warningf("block service: skip %s after auto-create: %v", path, err)
|
|
continue
|
|
}
|
|
} else {
|
|
glog.Warningf("block service: skip %s: %v", path, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Derive IQN from filename: vol1.blk -> iqn.2024-01.com.seaweedfs:vol.vol1
|
|
name := strings.TrimSuffix(entry.Name(), ".blk")
|
|
iqn := iqnPrefix + name
|
|
adapter := blockvol.NewBlockVolAdapter(vol)
|
|
bs.targetServer.AddVolume(iqn, adapter)
|
|
glog.V(0).Infof("block service: registered %s as %s", path, iqn)
|
|
}
|
|
|
|
// Start iSCSI target in background.
|
|
go func() {
|
|
if err := bs.targetServer.ListenAndServe(); err != nil {
|
|
glog.Warningf("block service: iSCSI target stopped: %v", err)
|
|
}
|
|
}()
|
|
|
|
glog.V(0).Infof("block service: iSCSI target started on %s", listenAddr)
|
|
return bs
|
|
}
|
|
|
|
// Shutdown gracefully stops the iSCSI target and closes all block volumes.
|
|
func (bs *BlockService) Shutdown() {
|
|
if bs == nil {
|
|
return
|
|
}
|
|
glog.V(0).Infof("block service: shutting down...")
|
|
if bs.targetServer != nil {
|
|
bs.targetServer.Close()
|
|
}
|
|
bs.blockStore.Close()
|
|
glog.V(0).Infof("block service: shut down")
|
|
}
|