Files
seaweedfs/weed/storage/blockvol/nvme/protocol.go
T
Ping QiuandClaude Opus 4.6 3557ae283f feat: Phase 10 CP10-3 -- NVMe/TCP Tier 1 optimizations, WAL admission control, benchmark platform
CP10-3 Tier 1 optimizations (T1-T4):
- TCP_NODELAY + 256KB socket buffers on NVMe/TCP connections
- Response batching: all C2H data chunks + CapsuleResp in single flush
- Tiered buffer pool (4KB/64KB/256KB sync.Pool) for write payloads
- Configurable MaxH2CDataLength wiring through controller/IC/chunking

BUG-CP103-1: NVMe write retry with jittered backoff for transient WAL pressure
- writeWithRetry() with bounded backoff [50/200/800ms]
- throttleOnWALPressure() pre-write delay above 90% WAL usage
- WALPressureProvider interface + NVMeAdapter.WALPressure()

BUG-CP103-2: Volume-level WAL admission control
- WALAdmission with counting semaphore (max concurrent writers)
- Soft watermark (0.7): small delay to desynchronize herd
- Hard watermark (0.9): block until flusher drains
- Single-deadline budget shared across watermark wait + semaphore
- Close-aware during both watermark and semaphore waits
- Wired into BlockVol.WriteLBA() and Trim()

Benchmark platform enhancements:
- NVMe benchmark actions and scenarios (A/B, CW sweep, IOQ sweep)
- Database benchmark actions (SQLite, pgbench)
- K8s operator QA reconciler tests
- New testrunner scenarios for HA, fault injection, CSI lifecycle

Test counts: 213 NVMe + 625 engine + operator + testrunner tests, all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 17:44:01 -07:00

504 lines
15 KiB
Go

// Package nvme implements an NVMe/TCP target for SeaweedFS BlockVol.
//
// This package provides a functionally correct NVMe-oF over TCP transport
// that shares the same BlockVol engine, fencing, replication, and failover
// as the iSCSI target.
package nvme
import (
"encoding/binary"
"fmt"
)
// ---------- PDU type codes ----------
const (
pduICReq uint8 = 0x0 // Initialization Connection Request
pduICResp uint8 = 0x1 // Initialization Connection Response
pduH2CTermReq uint8 = 0x2 // Host-to-Controller Termination Request
pduC2HTermReq uint8 = 0x3 // Controller-to-Host Termination Request
pduCapsuleCmd uint8 = 0x4 // NVMe Capsule Command
pduCapsuleResp uint8 = 0x5 // NVMe Capsule Response
pduH2CData uint8 = 0x6 // Host-to-Controller Data Transfer
pduC2HData uint8 = 0x7 // Controller-to-Host Data Transfer
pduR2T uint8 = 0x9 // Ready-to-Transfer
)
// ---------- Admin command opcodes ----------
const (
adminFlush uint8 = 0x00 // NVM Flush (admin context unused here)
adminGetLogPage uint8 = 0x02
adminIdentify uint8 = 0x06
adminAbort uint8 = 0x08
adminSetFeatures uint8 = 0x09
adminGetFeatures uint8 = 0x0A
adminAsyncEvent uint8 = 0x0C
adminKeepAlive uint8 = 0x18
adminFabric uint8 = 0x7F // Fabric-specific commands
)
// ---------- IO command opcodes ----------
const (
ioFlush uint8 = 0x00
ioWrite uint8 = 0x01
ioRead uint8 = 0x02
ioWriteZeros uint8 = 0x08
)
// ---------- Fabric command types (FCType) ----------
const (
fcPropertySet uint8 = 0x00
fcConnect uint8 = 0x01
fcPropertyGet uint8 = 0x04
fcDisconnect uint8 = 0x08
)
// ---------- Feature identifiers ----------
const (
fidNumberOfQueues uint8 = 0x07
fidAsyncEventConfig uint8 = 0x0B
fidKeepAliveTimer uint8 = 0x0F
)
// ---------- Identify CNS types ----------
const (
cnsIdentifyNamespace uint8 = 0x00
cnsIdentifyController uint8 = 0x01
cnsActiveNSList uint8 = 0x02
cnsNSDescriptorList uint8 = 0x03
)
// ---------- Log page identifiers ----------
const (
logPageError uint8 = 0x01
logPageSMART uint8 = 0x02
logPageANA uint8 = 0x0C
)
// ---------- Property register offsets ----------
const (
propCAP uint32 = 0x00 // Controller Capabilities
propVS uint32 = 0x08 // Version
propCC uint32 = 0x14 // Controller Configuration
propCSTS uint32 = 0x1C // Controller Status
)
// ---------- ANA states ----------
const (
anaOptimized uint8 = 0x01
anaNonOptimized uint8 = 0x02
anaInaccessible uint8 = 0x03
anaPersistentLoss uint8 = 0x04
anaChange uint8 = 0x0F
)
// ---------- Misc constants ----------
const (
commonHeaderSize = 8
maxHeaderSize = 128
maxH2CDataLen = 0x8000 // 32 KB
capsuleCmdSize = 64 // CapsuleCommand specific header size (after CommonHeader)
capsuleRespSize = 16 // CapsuleResponse specific header size
c2hDataHdrSize = 16 // C2HDataHeader specific header size
h2cDataHdrSize = 16 // H2CDataHeader specific header size
r2tHdrSize = 16 // R2THeader specific header size
icBodySize = 120 // ICReq/ICResp body size (after CommonHeader)
connectDataSize = 1024
// Total header lengths including CommonHeader
capsuleCmdHdrLen = commonHeaderSize + capsuleCmdSize // 72
capsuleRespHdrLen = commonHeaderSize + capsuleRespSize // 24
c2hDataHdrLen = commonHeaderSize + c2hDataHdrSize // 24
icHdrLen = commonHeaderSize + icBodySize // 128
commandBitDeallocate = 1 << 25
nvmeVersion14 uint32 = 0x00010400 // NVMe 1.4
// C2HData flags
c2hFlagLast uint8 = 0x04
)
// ---------- CommonHeader (8 bytes) ----------
// CommonHeader is the 8-byte preamble of every NVMe/TCP PDU.
type CommonHeader struct {
Type uint8
Flags uint8
HeaderLength uint8
DataOffset uint8
DataLength uint32
}
func (h *CommonHeader) Marshal(buf []byte) {
buf[0] = h.Type
buf[1] = h.Flags
buf[2] = h.HeaderLength
buf[3] = h.DataOffset
binary.LittleEndian.PutUint32(buf[4:], h.DataLength)
}
func (h *CommonHeader) Unmarshal(buf []byte) {
h.Type = buf[0]
h.Flags = buf[1]
h.HeaderLength = buf[2]
h.DataOffset = buf[3]
h.DataLength = binary.LittleEndian.Uint32(buf[4:])
}
func (h *CommonHeader) String() string {
return fmt.Sprintf("PDU{type=0x%x hlen=%d doff=%d dlen=%d}",
h.Type, h.HeaderLength, h.DataOffset, h.DataLength)
}
// ---------- PDU interface ----------
// PDU is the interface for all NVMe/TCP PDU-specific headers.
type PDU interface {
Marshal([]byte)
Unmarshal([]byte)
}
// ---------- ICRequest (120-byte body) ----------
// ICRequest is the host-to-controller initialization request.
type ICRequest struct {
PDUFormatVersion uint16
PDUDataAlignment uint8
PDUDataDigest uint8
PDUMaxR2T uint32
// remaining 112 bytes reserved
}
func (r *ICRequest) Marshal(buf []byte) {
// zero out the full 120-byte body
for i := range buf[:icBodySize] {
buf[i] = 0
}
binary.LittleEndian.PutUint16(buf[0:], r.PDUFormatVersion)
buf[2] = r.PDUDataAlignment
buf[3] = r.PDUDataDigest
binary.LittleEndian.PutUint32(buf[4:], r.PDUMaxR2T)
}
func (r *ICRequest) Unmarshal(buf []byte) {
r.PDUFormatVersion = binary.LittleEndian.Uint16(buf[0:])
r.PDUDataAlignment = buf[2]
r.PDUDataDigest = buf[3]
r.PDUMaxR2T = binary.LittleEndian.Uint32(buf[4:])
}
// ---------- ICResponse (120-byte body) ----------
// ICResponse is the controller-to-host initialization response.
type ICResponse struct {
PDUFormatVersion uint16
PDUDataAlignment uint8
PDUDataDigest uint8
MaxH2CDataLength uint32
// remaining 112 bytes reserved
}
func (r *ICResponse) Marshal(buf []byte) {
for i := range buf[:icBodySize] {
buf[i] = 0
}
binary.LittleEndian.PutUint16(buf[0:], r.PDUFormatVersion)
buf[2] = r.PDUDataAlignment
buf[3] = r.PDUDataDigest
binary.LittleEndian.PutUint32(buf[4:], r.MaxH2CDataLength)
}
func (r *ICResponse) Unmarshal(buf []byte) {
r.PDUFormatVersion = binary.LittleEndian.Uint16(buf[0:])
r.PDUDataAlignment = buf[2]
r.PDUDataDigest = buf[3]
r.MaxH2CDataLength = binary.LittleEndian.Uint32(buf[4:])
}
// ---------- CapsuleCommand (64-byte specific header) ----------
// CapsuleCommand is the 64-byte NVMe command capsule.
type CapsuleCommand struct {
OpCode uint8
PRP uint8
CID uint16
FCType uint8 // Fabric command type (only for OpCode=0x7F)
NSID uint32 // Namespace ID (bytes 4-7 of NVMe SQE after opcode/flags/CID)
DPTR [16]byte // Data pointer
D10 uint32
D11 uint32
D12 uint32
D13 uint32
D14 uint32
D15 uint32
}
// Lba returns the starting LBA from D10:D11 (Read/Write commands).
func (c *CapsuleCommand) Lba() uint64 {
return uint64(c.D11)<<32 | uint64(c.D10)
}
// LbaLength returns the number of logical blocks (0-based in D12, actual = D12&0xFFFF + 1).
func (c *CapsuleCommand) LbaLength() uint32 {
return c.D12&0xFFFF + 1
}
func (c *CapsuleCommand) Marshal(buf []byte) {
for i := range buf[:capsuleCmdSize] {
buf[i] = 0
}
buf[0] = c.OpCode
buf[1] = c.PRP
binary.LittleEndian.PutUint16(buf[2:], c.CID)
// Bytes 4-7: NSID for normal commands, FCType at byte 4 for Fabric (0x7F).
// They share the same offset per NVMe spec.
if c.OpCode == adminFabric {
buf[4] = c.FCType
} else {
binary.LittleEndian.PutUint32(buf[4:], c.NSID)
}
copy(buf[24:40], c.DPTR[:])
binary.LittleEndian.PutUint32(buf[40:], c.D10)
binary.LittleEndian.PutUint32(buf[44:], c.D11)
binary.LittleEndian.PutUint32(buf[48:], c.D12)
binary.LittleEndian.PutUint32(buf[52:], c.D13)
binary.LittleEndian.PutUint32(buf[56:], c.D14)
binary.LittleEndian.PutUint32(buf[60:], c.D15)
}
func (c *CapsuleCommand) Unmarshal(buf []byte) {
c.OpCode = buf[0]
c.PRP = buf[1]
c.CID = binary.LittleEndian.Uint16(buf[2:])
c.FCType = buf[4]
c.NSID = binary.LittleEndian.Uint32(buf[4:])
copy(c.DPTR[:], buf[24:40])
c.D10 = binary.LittleEndian.Uint32(buf[40:])
c.D11 = binary.LittleEndian.Uint32(buf[44:])
c.D12 = binary.LittleEndian.Uint32(buf[48:])
c.D13 = binary.LittleEndian.Uint32(buf[52:])
c.D14 = binary.LittleEndian.Uint32(buf[56:])
c.D15 = binary.LittleEndian.Uint32(buf[60:])
}
func (c *CapsuleCommand) String() string {
return fmt.Sprintf("CapsuleCmd{op=0x%02x cid=%d nsid=%d}", c.OpCode, c.CID, c.NSID)
}
// ---------- CapsuleResponse (16-byte specific header) ----------
// CapsuleResponse is the NVMe completion queue entry (16 bytes).
type CapsuleResponse struct {
DW0 uint32 // Command-specific DWord 0 (also FabricResponse bytes 0-3)
DW1 uint32 // Command-specific DWord 1 (also FabricResponse bytes 4-7)
SQHD uint16 // Submission Queue Head Pointer
QueueID uint16
CID uint16
Status uint16 // Status field: DNR(15) | More(14) | SCT(13:9) | SC(8:1) | P(0)
}
func (r *CapsuleResponse) Marshal(buf []byte) {
binary.LittleEndian.PutUint32(buf[0:], r.DW0)
binary.LittleEndian.PutUint32(buf[4:], r.DW1)
binary.LittleEndian.PutUint16(buf[8:], r.SQHD)
binary.LittleEndian.PutUint16(buf[10:], r.QueueID)
binary.LittleEndian.PutUint16(buf[12:], r.CID)
binary.LittleEndian.PutUint16(buf[14:], r.Status)
}
func (r *CapsuleResponse) Unmarshal(buf []byte) {
r.DW0 = binary.LittleEndian.Uint32(buf[0:])
r.DW1 = binary.LittleEndian.Uint32(buf[4:])
r.SQHD = binary.LittleEndian.Uint16(buf[8:])
r.QueueID = binary.LittleEndian.Uint16(buf[10:])
r.CID = binary.LittleEndian.Uint16(buf[12:])
r.Status = binary.LittleEndian.Uint16(buf[14:])
}
func (r *CapsuleResponse) String() string {
return fmt.Sprintf("CapsuleResp{sqhd=%d qid=%d cid=%d status=0x%04x}",
r.SQHD, r.QueueID, r.CID, r.Status)
}
// ---------- C2HDataHeader (16-byte specific header) ----------
// C2HDataHeader is the controller-to-host data transfer header.
type C2HDataHeader struct {
CCCID uint16 // Command Capsule CID
_ uint16 // reserved
DATAO uint32 // Data offset within the total transfer
DATAL uint32 // Data length in this PDU
_pad uint32 // reserved
}
func (h *C2HDataHeader) Marshal(buf []byte) {
for i := range buf[:c2hDataHdrSize] {
buf[i] = 0
}
binary.LittleEndian.PutUint16(buf[0:], h.CCCID)
binary.LittleEndian.PutUint32(buf[4:], h.DATAO)
binary.LittleEndian.PutUint32(buf[8:], h.DATAL)
}
func (h *C2HDataHeader) Unmarshal(buf []byte) {
h.CCCID = binary.LittleEndian.Uint16(buf[0:])
h.DATAO = binary.LittleEndian.Uint32(buf[4:])
h.DATAL = binary.LittleEndian.Uint32(buf[8:])
}
// ---------- R2THeader (16-byte specific header) ----------
// R2THeader is the Ready-to-Transfer PDU specific header.
type R2THeader struct {
CCCID uint16 // Command Capsule CID
TAG uint16 // R2T Tag (echoed by host in H2CData)
DATAO uint32 // Data offset
DATAL uint32 // Data length requested
_pad uint32
}
func (h *R2THeader) Marshal(buf []byte) {
for i := range buf[:r2tHdrSize] {
buf[i] = 0
}
binary.LittleEndian.PutUint16(buf[0:], h.CCCID)
binary.LittleEndian.PutUint16(buf[2:], h.TAG)
binary.LittleEndian.PutUint32(buf[4:], h.DATAO)
binary.LittleEndian.PutUint32(buf[8:], h.DATAL)
}
func (h *R2THeader) Unmarshal(buf []byte) {
h.CCCID = binary.LittleEndian.Uint16(buf[0:])
h.TAG = binary.LittleEndian.Uint16(buf[2:])
h.DATAO = binary.LittleEndian.Uint32(buf[4:])
h.DATAL = binary.LittleEndian.Uint32(buf[8:])
}
// ---------- H2CDataHeader (16-byte specific header) ----------
// H2CDataHeader is the host-to-controller data transfer header.
type H2CDataHeader struct {
CCCID uint16 // Command Capsule CID
TAG uint16 // Matches R2T Tag
DATAO uint32 // Data offset
DATAL uint32 // Data length in this PDU
_pad uint32
}
func (h *H2CDataHeader) Marshal(buf []byte) {
for i := range buf[:h2cDataHdrSize] {
buf[i] = 0
}
binary.LittleEndian.PutUint16(buf[0:], h.CCCID)
binary.LittleEndian.PutUint16(buf[2:], h.TAG)
binary.LittleEndian.PutUint32(buf[4:], h.DATAO)
binary.LittleEndian.PutUint32(buf[8:], h.DATAL)
}
func (h *H2CDataHeader) Unmarshal(buf []byte) {
h.CCCID = binary.LittleEndian.Uint16(buf[0:])
h.TAG = binary.LittleEndian.Uint16(buf[2:])
h.DATAO = binary.LittleEndian.Uint32(buf[4:])
h.DATAL = binary.LittleEndian.Uint32(buf[8:])
}
// ---------- ConnectData (1024 bytes, payload of Fabric Connect) ----------
// ConnectData is the 1024-byte payload sent with a Fabric Connect command.
type ConnectData struct {
HostID [16]byte // Host UUID
CNTLID uint16 // Requested controller ID (0xFFFF = new)
SubNQN string // Subsystem NQN
HostNQN string // Host NQN
}
func (d *ConnectData) Marshal(buf []byte) {
for i := range buf[:connectDataSize] {
buf[i] = 0
}
copy(buf[0:16], d.HostID[:])
binary.LittleEndian.PutUint16(buf[16:], d.CNTLID)
copyNQN(buf[256:512], d.SubNQN)
copyNQN(buf[512:768], d.HostNQN)
}
func (d *ConnectData) Unmarshal(buf []byte) {
copy(d.HostID[:], buf[0:16])
d.CNTLID = binary.LittleEndian.Uint16(buf[16:])
d.SubNQN = extractNQN(buf[256:512])
d.HostNQN = extractNQN(buf[512:768])
}
// copyNQN writes a NUL-terminated string into a fixed-size buffer.
func copyNQN(dst []byte, s string) {
n := copy(dst, s)
if n < len(dst) {
dst[n] = 0
}
}
// extractNQN reads a NUL-terminated string from a fixed-size buffer.
func extractNQN(buf []byte) string {
for i, b := range buf {
if b == 0 {
return string(buf[:i])
}
}
return string(buf)
}
// ---------- Status word encoding ----------
// StatusWord encodes NVMe status: DNR(15) | More(14) | SCT(13:9) | SC(8:1) | P(0)
//
// StatusWord = (DNR << 15) | (SCT << 9) | (SC << 1)
type StatusWord uint16
// MakeStatus constructs a status word from SCT, SC, and DNR flag.
func MakeStatus(sct, sc uint8, dnr bool) StatusWord {
w := uint16(sct)<<9 | uint16(sc)<<1
if dnr {
w |= 1 << 15
}
return StatusWord(w)
}
// StatusSuccess is the zero-value success status.
const StatusSuccess StatusWord = 0
// Pre-defined status words used in the NVMe target.
var (
StatusInvalidOpcode = MakeStatus(0, 0x01, true) // Generic: Invalid Command Opcode
StatusInvalidField = MakeStatus(0, 0x02, true) // Generic: Invalid Field in Command
StatusInternalError = MakeStatus(0, 0x06, false) // Generic: Internal Error (retryable)
StatusInternalErrorDNR = MakeStatus(0, 0x06, true) // Generic: Internal Error (permanent)
StatusNSNotReady = MakeStatus(0, 0x82, false) // Generic: Namespace Not Ready (retryable)
StatusNSNotReadyDNR = MakeStatus(0, 0x82, true) // Generic: Namespace Not Ready (permanent)
StatusLBAOutOfRange = MakeStatus(0, 0x80, true) // Generic: LBA Out of Range
StatusMediaWriteFault = MakeStatus(2, 0x80, false) // Media: Write Fault
StatusMediaReadError = MakeStatus(2, 0x81, false) // Media: Uncorrectable Read Error
)
func (s StatusWord) SCT() uint8 { return uint8((s >> 9) & 0x07) }
func (s StatusWord) SC() uint8 { return uint8((s >> 1) & 0xFF) }
func (s StatusWord) DNR() bool { return s&(1<<15) != 0 }
func (s StatusWord) IsError() bool { return s != StatusSuccess }
func (s StatusWord) String() string {
if s == StatusSuccess {
return "Success"
}
return fmt.Sprintf("Status{sct=%d sc=0x%02x dnr=%v}", s.SCT(), s.SC(), s.DNR())
}