optimize the perf and support more features
This commit is contained in:
@@ -17,16 +17,105 @@ limitations under the License.
|
||||
package iscsit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
"github.com/gostor/gotgt/pkg/util"
|
||||
"github.com/gostor/gotgt/pkg/util/numa"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Object pools to reduce GC pressure
|
||||
var (
|
||||
// commandPool reuses ISCSICommand objects
|
||||
commandPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &ISCSICommand{}
|
||||
},
|
||||
}
|
||||
|
||||
// bufferPool reuses small buffers for BHS reading
|
||||
bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
buf := make([]byte, BHS_SIZE)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
// numaBufferPool NUMA-aware buffer pool for larger I/O operations
|
||||
numaBufferPool *numa.NUMABufferPool
|
||||
numaPoolOnce sync.Once
|
||||
)
|
||||
|
||||
// initNUMAPool initializes the NUMA-aware buffer pool
|
||||
func initNUMAPool() {
|
||||
numaPoolOnce.Do(func() {
|
||||
numaBufferPool = numa.NewNUMABufferPool(&numa.BufferPoolConfig{
|
||||
BufferSize: 256 * 1024, // 256KB for I/O buffers
|
||||
PerNodePoolSize: 512,
|
||||
EnableNUMA: numa.Available(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// getCommand gets an ISCSICommand from the pool
|
||||
func getCommand() *ISCSICommand {
|
||||
return commandPool.Get().(*ISCSICommand)
|
||||
}
|
||||
|
||||
// putCommand puts an ISCSICommand back to the pool
|
||||
func putCommand(cmd *ISCSICommand) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
// Clear sensitive data
|
||||
cmd.RawData = nil
|
||||
cmd.RawHeader = nil
|
||||
cmd.CDB = nil
|
||||
cmd.DataLen = 0
|
||||
*cmd = ISCSICommand{}
|
||||
commandPool.Put(cmd)
|
||||
}
|
||||
|
||||
// getBuffer gets a buffer from the pool
|
||||
func getBuffer() []byte {
|
||||
return *bufferPool.Get().(*[]byte)
|
||||
}
|
||||
|
||||
// putBuffer puts a buffer back to the pool
|
||||
func putBuffer(buf []byte) {
|
||||
if cap(buf) >= BHS_SIZE {
|
||||
bufferPool.Put(&buf)
|
||||
}
|
||||
}
|
||||
|
||||
// getIOBuffer gets a NUMA-aware I/O buffer for larger data operations
|
||||
func getIOBuffer(size int) []byte {
|
||||
initNUMAPool()
|
||||
if size <= numaBufferPool.GetConfig().BufferSize {
|
||||
return numaBufferPool.Get()[:size]
|
||||
}
|
||||
return make([]byte, size)
|
||||
}
|
||||
|
||||
// putIOBuffer puts a NUMA-aware I/O buffer back to the pool
|
||||
func putIOBuffer(buf []byte) {
|
||||
if numaBufferPool != nil && cap(buf) >= numaBufferPool.GetConfig().BufferSize {
|
||||
numaBufferPool.Put(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// NUMAStats returns NUMA buffer pool statistics
|
||||
func NUMAStats() numa.PoolStats {
|
||||
if numaBufferPool == nil {
|
||||
return numa.PoolStats{}
|
||||
}
|
||||
return numaBufferPool.Stats()
|
||||
}
|
||||
|
||||
type OpCode int
|
||||
|
||||
const (
|
||||
@@ -164,6 +253,8 @@ func (cmd *ISCSICommand) Bytes() []byte {
|
||||
return cmd.scsiTMFRespBytes()
|
||||
case OpReady:
|
||||
return cmd.r2tRespBytes()
|
||||
case OpAsync:
|
||||
return cmd.asyncMsgBytes()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -237,7 +328,7 @@ func parseHeader(data []byte) (*ISCSICommand, error) {
|
||||
m.CmdSN = uint32(ParseUint(data[24:28]))
|
||||
m.Read = data[1]&0x40 == 0x40
|
||||
m.Write = data[1]&0x20 == 0x20
|
||||
m.CDB = data[32:48]
|
||||
m.CDB = append([]byte{}, data[32:48]...)
|
||||
m.ExpStatSN = uint32(ParseUint(data[28:32]))
|
||||
m.SCSIOpCode = m.CDB[0]
|
||||
SCSIOpcode := api.SCSICommandType(m.SCSIOpCode)
|
||||
@@ -290,9 +381,12 @@ func parseHeader(data []byte) (*ISCSICommand, error) {
|
||||
}
|
||||
|
||||
func (m *ISCSICommand) scsiCmdRespBytes() []byte {
|
||||
// rfc7143 11.4
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteByte(byte(OpSCSIResp))
|
||||
// rfc7143 11.4 - BHS 48 bytes + data (4-byte aligned)
|
||||
rawDataLen := len(m.RawData)
|
||||
padding := (4 - rawDataLen%4) % 4
|
||||
buf := make([]byte, 48+rawDataLen+padding)
|
||||
|
||||
buf[0] = byte(OpSCSIResp)
|
||||
var flag byte = 0x80
|
||||
if m.Resid > 0 {
|
||||
if m.Resid > m.ExpectedDataLen {
|
||||
@@ -301,50 +395,46 @@ func (m *ISCSICommand) scsiCmdRespBytes() []byte {
|
||||
flag |= 0x02
|
||||
}
|
||||
}
|
||||
buf.WriteByte(flag)
|
||||
buf.WriteByte(byte(m.SCSIResponse))
|
||||
buf.WriteByte(byte(m.Status))
|
||||
buf[1] = flag
|
||||
buf[2] = byte(m.SCSIResponse)
|
||||
buf[3] = byte(m.Status)
|
||||
|
||||
buf.WriteByte(0x00)
|
||||
buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8
|
||||
// Skip through to byte 16
|
||||
for i := 0; i < 8; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
|
||||
for i := 0; i < 4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
|
||||
for i := 0; i < 2*4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.Resid))[4:])
|
||||
buf.Write(m.RawData)
|
||||
dl := len(m.RawData)
|
||||
for dl%4 > 0 {
|
||||
dl++
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
// byte 4 is reserved (0)
|
||||
// Write data length (24-bit big-endian) at bytes 5-7
|
||||
buf[5] = byte(rawDataLen >> 16)
|
||||
buf[6] = byte(rawDataLen >> 8)
|
||||
buf[7] = byte(rawDataLen)
|
||||
// bytes 9-15 are reserved (0)
|
||||
// TaskTag at bytes 16-19 (32-bit big-endian)
|
||||
util.MarshalUint32To(buf[16:], m.TaskTag)
|
||||
// bytes 20-23 are reserved (0)
|
||||
// StatSN at bytes 24-27
|
||||
util.MarshalUint32To(buf[24:], m.StatSN)
|
||||
// ExpCmdSN at bytes 28-31
|
||||
util.MarshalUint32To(buf[28:], m.ExpCmdSN)
|
||||
// MaxCmdSN at bytes 32-35
|
||||
util.MarshalUint32To(buf[32:], m.MaxCmdSN)
|
||||
// bytes 36-43 are reserved (0)
|
||||
// Resid at bytes 44-47
|
||||
util.MarshalUint32To(buf[44:], m.Resid)
|
||||
copy(buf[48:], m.RawData)
|
||||
// padding bytes are already zero
|
||||
|
||||
return buf.Bytes()
|
||||
return buf
|
||||
}
|
||||
|
||||
func (m *ISCSICommand) dataInBytes() []byte {
|
||||
// rfc7143 11.7
|
||||
dl := m.DataLen
|
||||
for dl%4 > 0 {
|
||||
dl++
|
||||
}
|
||||
var buf = make([]byte, (48 + dl))
|
||||
// Calculate padded length using bit operation instead of loop
|
||||
dl := (m.DataLen + 3) &^ 3 // Round up to multiple of 4
|
||||
buf := make([]byte, 48+dl)
|
||||
|
||||
buf[0] = byte(OpSCSIIn)
|
||||
var flag byte
|
||||
if m.FinalInSeq || m.Final == true {
|
||||
if m.FinalInSeq || m.Final {
|
||||
flag |= 0x80
|
||||
}
|
||||
if m.HasStatus && m.Final == true {
|
||||
if m.HasStatus && m.Final {
|
||||
flag |= 0x01
|
||||
}
|
||||
log.Debugf("resid: %v, ExpectedDataLen: %v", m.Resid, m.ExpectedDataLen)
|
||||
@@ -356,22 +446,22 @@ func (m *ISCSICommand) dataInBytes() []byte {
|
||||
}
|
||||
}
|
||||
buf[1] = flag
|
||||
//buf.WriteByte(0x00)
|
||||
if m.HasStatus && m.Final == true {
|
||||
flag = byte(m.Status)
|
||||
if m.HasStatus && m.Final {
|
||||
buf[3] = byte(m.Status)
|
||||
}
|
||||
//buf.WriteByte(flag)
|
||||
buf[3] = flag
|
||||
copy(buf[5:], util.MarshalUint64(uint64(m.DataLen))[5:])
|
||||
// Data length (24-bit) at bytes 5-7
|
||||
buf[5] = byte(m.DataLen >> 16)
|
||||
buf[6] = byte(m.DataLen >> 8)
|
||||
buf[7] = byte(m.DataLen)
|
||||
// Skip through to byte 16 Since A bit is not set 11.7.4
|
||||
copy(buf[16:], util.MarshalUint32(m.TaskTag))
|
||||
copy(buf[24:], util.MarshalUint32(m.StatSN))
|
||||
copy(buf[28:], util.MarshalUint32(m.ExpCmdSN))
|
||||
copy(buf[32:], util.MarshalUint32(m.MaxCmdSN))
|
||||
copy(buf[36:], util.MarshalUint32(m.DataSN))
|
||||
copy(buf[40:], util.MarshalUint32(m.BufferOffset))
|
||||
copy(buf[44:], util.MarshalUint32(m.Resid))
|
||||
if m.ExpectedDataLen != 0 {
|
||||
util.MarshalUint32To(buf[16:], m.TaskTag)
|
||||
util.MarshalUint32To(buf[24:], m.StatSN)
|
||||
util.MarshalUint32To(buf[28:], m.ExpCmdSN)
|
||||
util.MarshalUint32To(buf[32:], m.MaxCmdSN)
|
||||
util.MarshalUint32To(buf[36:], m.DataSN)
|
||||
util.MarshalUint32To(buf[40:], m.BufferOffset)
|
||||
util.MarshalUint32To(buf[44:], m.Resid)
|
||||
if m.DataLen != 0 {
|
||||
copy(buf[48:], m.RawData[m.BufferOffset:m.BufferOffset+uint32(m.DataLen)])
|
||||
}
|
||||
|
||||
@@ -379,8 +469,13 @@ func (m *ISCSICommand) dataInBytes() []byte {
|
||||
}
|
||||
|
||||
func (m *ISCSICommand) textRespBytes() []byte {
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteByte(byte(OpTextResp))
|
||||
// Pre-calculate required capacity: BHS(48 bytes) + data (4-byte aligned)
|
||||
dataLen := len(m.RawData)
|
||||
padding := (4 - dataLen%4) % 4
|
||||
|
||||
buf := make([]byte, 48+dataLen+padding)
|
||||
|
||||
buf[0] = byte(OpTextResp)
|
||||
var b byte
|
||||
if m.Final {
|
||||
b |= 0x80
|
||||
@@ -389,122 +484,149 @@ func (m *ISCSICommand) textRespBytes() []byte {
|
||||
b |= 0x40
|
||||
}
|
||||
// byte 1
|
||||
buf.WriteByte(b)
|
||||
buf[1] = b
|
||||
|
||||
b = 0
|
||||
buf.WriteByte(b)
|
||||
buf.WriteByte(b)
|
||||
buf.WriteByte(b)
|
||||
buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8
|
||||
// Skip through to byte 12
|
||||
for i := 0; i < 2*4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
|
||||
for i := 0; i < 4; i++ {
|
||||
buf.WriteByte(0xff)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
|
||||
for i := 0; i < 3*4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
rd := m.RawData
|
||||
for len(rd)%4 != 0 {
|
||||
rd = append(rd, 0)
|
||||
}
|
||||
buf.Write(rd)
|
||||
return buf.Bytes()
|
||||
// bytes 2,3,4 reserved (0)
|
||||
// bytes 5-8: data segment length (24-bit)
|
||||
buf[5] = byte(dataLen >> 16)
|
||||
buf[6] = byte(dataLen >> 8)
|
||||
buf[7] = byte(dataLen)
|
||||
// bytes 8-15 are reserved (0)
|
||||
// bytes 16-19: TaskTag
|
||||
util.MarshalUint32To(buf[16:], m.TaskTag)
|
||||
// bytes 20-23: 0xffffffff
|
||||
buf[20] = 0xff
|
||||
buf[21] = 0xff
|
||||
buf[22] = 0xff
|
||||
buf[23] = 0xff
|
||||
// bytes 24-27: StatSN
|
||||
util.MarshalUint32To(buf[24:], m.StatSN)
|
||||
// bytes 28-31: ExpCmdSN
|
||||
util.MarshalUint32To(buf[28:], m.ExpCmdSN)
|
||||
// bytes 32-35: MaxCmdSN
|
||||
util.MarshalUint32To(buf[32:], m.MaxCmdSN)
|
||||
// bytes 36-47 are reserved (0)
|
||||
// Copy data
|
||||
copy(buf[48:], m.RawData)
|
||||
// padding bytes are already zero
|
||||
return buf
|
||||
}
|
||||
|
||||
func (m *ISCSICommand) noopInBytes() []byte {
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteByte(byte(OpNoopIn))
|
||||
var b byte
|
||||
b |= 0x80
|
||||
// byte 1
|
||||
buf.WriteByte(b)
|
||||
// rfc7143 11.11 - BHS 48 bytes + data (4-byte aligned)
|
||||
rawDataLen := len(m.RawData)
|
||||
padding := (4 - rawDataLen%4) % 4
|
||||
buf := make([]byte, 48+rawDataLen+padding)
|
||||
|
||||
b = 0
|
||||
buf.WriteByte(b)
|
||||
buf.WriteByte(b)
|
||||
buf.WriteByte(b)
|
||||
buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // 5-8
|
||||
// Skip through to byte 12
|
||||
for i := 0; i < 2*4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
|
||||
for i := 0; i < 4; i++ {
|
||||
buf.WriteByte(0xff)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
|
||||
for i := 0; i < 3*4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
rd := m.RawData
|
||||
for len(rd)%4 != 0 {
|
||||
rd = append(rd, 0)
|
||||
}
|
||||
buf.Write(rd)
|
||||
return buf.Bytes()
|
||||
buf[0] = byte(OpNoopIn)
|
||||
buf[1] = 0x80
|
||||
// bytes 2-3 are reserved (0)
|
||||
// bytes 4-7: data segment length (32-bit)
|
||||
util.MarshalUint32To(buf[4:], uint32(rawDataLen))
|
||||
// bytes 8-15 are reserved (0)
|
||||
// bytes 16-19: TaskTag
|
||||
util.MarshalUint32To(buf[16:], m.TaskTag)
|
||||
// bytes 20-23: 0xffffffff
|
||||
buf[20] = 0xff
|
||||
buf[21] = 0xff
|
||||
buf[22] = 0xff
|
||||
buf[23] = 0xff
|
||||
// bytes 24-27: StatSN
|
||||
util.MarshalUint32To(buf[24:], m.StatSN)
|
||||
// bytes 28-31: ExpCmdSN
|
||||
util.MarshalUint32To(buf[28:], m.ExpCmdSN)
|
||||
// bytes 32-35: MaxCmdSN
|
||||
util.MarshalUint32To(buf[32:], m.MaxCmdSN)
|
||||
// bytes 36-47 are reserved (0)
|
||||
copy(buf[48:], m.RawData)
|
||||
// padding bytes are already zero
|
||||
return buf
|
||||
}
|
||||
|
||||
func (m *ISCSICommand) scsiTMFRespBytes() []byte {
|
||||
// rfc7143 11.6
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteByte(byte(OpSCSITaskResp))
|
||||
buf.WriteByte(0x80)
|
||||
buf.WriteByte(m.Result)
|
||||
buf.WriteByte(0x00)
|
||||
|
||||
// Skip through to byte 16
|
||||
for i := 0; i < 3*4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
|
||||
for i := 0; i < 4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
|
||||
for i := 0; i < 3*4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
// rfc7143 11.6 - Fixed 48 bytes
|
||||
buf := make([]byte, 48)
|
||||
buf[0] = byte(OpSCSITaskResp)
|
||||
buf[1] = 0x80
|
||||
buf[2] = m.Result
|
||||
// byte 3 is reserved (0)
|
||||
// bytes 4-15 are reserved (0)
|
||||
// bytes 16-19: TaskTag
|
||||
util.MarshalUint32To(buf[16:], m.TaskTag)
|
||||
// bytes 20-23 are reserved (0)
|
||||
// bytes 24-27: StatSN
|
||||
util.MarshalUint32To(buf[24:], m.StatSN)
|
||||
// bytes 28-31: ExpCmdSN
|
||||
util.MarshalUint32To(buf[28:], m.ExpCmdSN)
|
||||
// bytes 32-35: MaxCmdSN
|
||||
util.MarshalUint32To(buf[32:], m.MaxCmdSN)
|
||||
// bytes 36-47 are reserved (0)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (m *ISCSICommand) r2tRespBytes() []byte {
|
||||
// rfc7143 11.8
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteByte(byte(OpReady))
|
||||
var b byte
|
||||
// rfc7143 11.8 - Fixed 48 bytes
|
||||
buf := make([]byte, 48)
|
||||
buf[0] = byte(OpReady)
|
||||
if m.Final {
|
||||
b |= 0x80
|
||||
buf[1] = 0x80
|
||||
}
|
||||
buf.WriteByte(b)
|
||||
buf.WriteByte(0x00)
|
||||
buf.WriteByte(0x00)
|
||||
|
||||
// Skip through to byte 16
|
||||
for i := 0; i < 3*4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
|
||||
for i := 0; i < 4; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.R2TSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.BufferOffset))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.DesiredLength))[4:])
|
||||
|
||||
return buf.Bytes()
|
||||
// bytes 2-15 are reserved (0)
|
||||
// bytes 16-19: TaskTag
|
||||
util.MarshalUint32To(buf[16:], m.TaskTag)
|
||||
// bytes 20-23 are reserved (0)
|
||||
// bytes 24-27: StatSN
|
||||
util.MarshalUint32To(buf[24:], m.StatSN)
|
||||
// bytes 28-31: ExpCmdSN
|
||||
util.MarshalUint32To(buf[28:], m.ExpCmdSN)
|
||||
// bytes 32-35: MaxCmdSN
|
||||
util.MarshalUint32To(buf[32:], m.MaxCmdSN)
|
||||
// bytes 36-39: R2TSN
|
||||
util.MarshalUint32To(buf[36:], m.R2TSN)
|
||||
// bytes 40-43: BufferOffset
|
||||
util.MarshalUint32To(buf[40:], m.BufferOffset)
|
||||
// bytes 44-47: DesiredLength
|
||||
util.MarshalUint32To(buf[44:], m.DesiredLength)
|
||||
return buf
|
||||
}
|
||||
|
||||
// asyncMsgBytes implements RFC 7143 section 11.10 - Asynchronous Message
|
||||
func (m *ISCSICommand) asyncMsgBytes() []byte {
|
||||
// rfc7143 11.10 - BHS 48 bytes + data (4-byte aligned)
|
||||
rawDataLen := len(m.RawData)
|
||||
padding := (4 - rawDataLen%4) % 4
|
||||
buf := make([]byte, 48+rawDataLen+padding)
|
||||
|
||||
buf[0] = byte(OpAsync)
|
||||
// byte 1: AsyncEvent in bits 0-4
|
||||
buf[1] = byte(m.SCSIOpCode & 0x1f)
|
||||
// bytes 2-3 are reserved (0)
|
||||
|
||||
// byte 4: 0x80 if AsyncEvent is 0 (SCSI Asynchronous Event)
|
||||
if m.SCSIOpCode == 0 {
|
||||
buf[4] = 0x80
|
||||
}
|
||||
|
||||
// bytes 5-7: data segment length (24-bit)
|
||||
buf[5] = byte(rawDataLen >> 16)
|
||||
buf[6] = byte(rawDataLen >> 8)
|
||||
buf[7] = byte(rawDataLen)
|
||||
// bytes 8-15: LUN (if applicable)
|
||||
copy(buf[8:], m.LUN[:])
|
||||
// bytes 16-19: Reserved (0)
|
||||
// bytes 20-23: Target Transfer Tag (0xffffffff for Async)
|
||||
buf[20] = 0xff
|
||||
buf[21] = 0xff
|
||||
buf[22] = 0xff
|
||||
buf[23] = 0xff
|
||||
// bytes 24-27: StatSN
|
||||
util.MarshalUint32To(buf[24:], m.StatSN)
|
||||
// bytes 28-31: ExpCmdSN
|
||||
util.MarshalUint32To(buf[28:], m.ExpCmdSN)
|
||||
// bytes 32-35: MaxCmdSN
|
||||
util.MarshalUint32To(buf[32:], m.MaxCmdSN)
|
||||
// bytes 36-43: Reserved (0)
|
||||
// bytes 44-47: Parameter1 and Parameter2 (context-specific)
|
||||
copy(buf[48:], m.RawData)
|
||||
return buf
|
||||
}
|
||||
|
||||
146
pkg/port/iscsit/cmd_test.go
Normal file
146
pkg/port/iscsit/cmd_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
Copyright 2024 The GoStor Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package iscsit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func TestGetPutCommand(t *testing.T) {
|
||||
// Test getting command object
|
||||
cmd1 := getCommand()
|
||||
if cmd1 == nil {
|
||||
t.Fatal("getCommand() returned nil")
|
||||
}
|
||||
|
||||
// Set some values
|
||||
cmd1.TaskTag = 12345
|
||||
cmd1.DataLen = 100
|
||||
cmd1.ExpCmdSN = 999
|
||||
|
||||
// Put back to pool
|
||||
putCommand(cmd1)
|
||||
|
||||
// Get again, verify if reused (may be reset)
|
||||
cmd2 := getCommand()
|
||||
if cmd2 == nil {
|
||||
t.Fatal("getCommand() returned nil after put")
|
||||
}
|
||||
|
||||
// Put back
|
||||
putCommand(cmd2)
|
||||
|
||||
// Test nil doesn't panic
|
||||
putCommand(nil)
|
||||
}
|
||||
|
||||
func TestGetPutBuffer(t *testing.T) {
|
||||
// Test getting buffer
|
||||
buf1 := getBuffer()
|
||||
if buf1 == nil {
|
||||
t.Fatal("getBuffer() returned nil")
|
||||
}
|
||||
if len(buf1) != BHS_SIZE {
|
||||
t.Errorf("expected buffer size %d, got %d", BHS_SIZE, len(buf1))
|
||||
}
|
||||
|
||||
// Modify buffer content
|
||||
for i := range buf1 {
|
||||
buf1[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
// Put back to pool
|
||||
putBuffer(buf1)
|
||||
|
||||
// Get again
|
||||
buf2 := getBuffer()
|
||||
if buf2 == nil {
|
||||
t.Fatal("getBuffer() returned nil after put")
|
||||
}
|
||||
if len(buf2) != BHS_SIZE {
|
||||
t.Errorf("expected buffer size %d, got %d", BHS_SIZE, len(buf2))
|
||||
}
|
||||
|
||||
putBuffer(buf2)
|
||||
|
||||
// Test small buffer won't be put into pool
|
||||
smallBuf := make([]byte, 10)
|
||||
putBuffer(smallBuf) // Should not panic
|
||||
|
||||
// Test nil doesn't panic
|
||||
putBuffer(nil)
|
||||
}
|
||||
|
||||
func TestBufferPoolReuse(t *testing.T) {
|
||||
// Get buffer and record address
|
||||
buf1 := getBuffer()
|
||||
ptr1 := uintptr(unsafe.Pointer(&buf1[0]))
|
||||
putBuffer(buf1)
|
||||
|
||||
// Get again, verify if reuse is possible (not guaranteed)
|
||||
buf2 := getBuffer()
|
||||
ptr2 := uintptr(unsafe.Pointer(&buf2[0]))
|
||||
putBuffer(buf2)
|
||||
|
||||
// If reused, addresses should be the same
|
||||
// If not reused, it's fine, this is a performance test
|
||||
t.Logf("First buffer pointer: %x, Second buffer pointer: %x, reused: %v",
|
||||
ptr1, ptr2, ptr1 == ptr2)
|
||||
}
|
||||
|
||||
func BenchmarkGetPutCommand(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
cmd := getCommand()
|
||||
cmd.TaskTag = 1
|
||||
putCommand(cmd)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkGetPutBuffer(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
buf := getBuffer()
|
||||
buf[0] = 1
|
||||
putBuffer(buf)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkAllocCommand 对比:不使用 pool 直接创建
|
||||
func BenchmarkAllocCommand(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
cmd := &ISCSICommand{}
|
||||
cmd.TaskTag = 1
|
||||
_ = cmd
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkAllocBuffer 对比:不使用 pool 直接创建
|
||||
func BenchmarkAllocBuffer(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
buf := make([]byte, BHS_SIZE)
|
||||
buf[0] = 1
|
||||
_ = buf
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -155,12 +155,12 @@ func (conn *iscsiConnection) buildRespPackage(oc OpCode, task *iscsiTask) error
|
||||
if task == nil {
|
||||
task = conn.rxTask
|
||||
}
|
||||
conn.resp = &ISCSICommand{
|
||||
StartTime: conn.req.StartTime,
|
||||
StatSN: conn.req.ExpStatSN,
|
||||
TaskTag: conn.req.TaskTag,
|
||||
ExpectedDataLen: conn.req.ExpectedDataLen,
|
||||
}
|
||||
// Get ISCSICommand from object pool
|
||||
conn.resp = getCommand()
|
||||
conn.resp.StartTime = conn.req.StartTime
|
||||
conn.resp.StatSN = conn.req.ExpStatSN
|
||||
conn.resp.TaskTag = conn.req.TaskTag
|
||||
conn.resp.ExpectedDataLen = conn.req.ExpectedDataLen
|
||||
if conn.session != nil {
|
||||
conn.resp.ExpCmdSN = conn.session.ExpCmdSN
|
||||
conn.resp.MaxCmdSN = conn.session.ExpCmdSN + conn.session.MaxQueueCommand
|
||||
|
||||
@@ -44,6 +44,86 @@ const (
|
||||
STATE_TERMINATE
|
||||
)
|
||||
|
||||
// tsihBitmap is a bitmap for efficient TSIH allocation/deallocation
|
||||
// Uses circular counter for O(1) allocation
|
||||
type tsihBitmap struct {
|
||||
mu sync.Mutex
|
||||
bitmap []uint64 // Each uint64 stores the usage status of 64 TSIHs
|
||||
next uint16 // Next candidate position for allocation
|
||||
used uint16 // Number of used TSIHs
|
||||
}
|
||||
|
||||
// newTSIHBitmap creates a new TSIH bitmap
|
||||
// Reserves 0 and 65535 as special values
|
||||
func newTSIHBitmap() *tsihBitmap {
|
||||
// Need 65536 bits = 1024 uint64s
|
||||
b := &tsihBitmap{
|
||||
bitmap: make([]uint64, 1024),
|
||||
next: 1, // Start from 1, 0 is reserved
|
||||
}
|
||||
// Mark 0 and 65535 as used (reserved values)
|
||||
b.bitmap[0] |= 1 << 0 // TSIH = 0
|
||||
b.bitmap[1023] |= 1 << 63 // TSIH = 65535
|
||||
b.used = 2
|
||||
return b
|
||||
}
|
||||
|
||||
// alloc allocates an available TSIH using circular search strategy
|
||||
func (b *tsihBitmap) alloc() uint16 {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if b.used >= ISCSI_MAX_TSIH-1 {
|
||||
return ISCSI_UNSPEC_TSIH
|
||||
}
|
||||
|
||||
start := b.next
|
||||
for {
|
||||
idx := b.next / 64
|
||||
bit := b.next % 64
|
||||
|
||||
if (b.bitmap[idx] & (1 << bit)) == 0 {
|
||||
// Found free slot
|
||||
b.bitmap[idx] |= 1 << bit
|
||||
b.used++
|
||||
result := b.next
|
||||
// Update next to next position
|
||||
b.next++
|
||||
if b.next >= ISCSI_MAX_TSIH {
|
||||
b.next = 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
b.next++
|
||||
if b.next >= ISCSI_MAX_TSIH {
|
||||
b.next = 1
|
||||
}
|
||||
if b.next == start {
|
||||
// Looped around without finding
|
||||
return ISCSI_UNSPEC_TSIH
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// release releases a TSIH
|
||||
func (b *tsihBitmap) release(tsih uint16) {
|
||||
if tsih == 0 || tsih == ISCSI_MAX_TSIH {
|
||||
return // Cannot release reserved values
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
idx := tsih / 64
|
||||
bit := tsih % 64
|
||||
|
||||
if (b.bitmap[idx] & (1 << bit)) != 0 {
|
||||
b.bitmap[idx] &^= 1 << bit
|
||||
b.used--
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
EnableStats bool
|
||||
CurrentHostIP string
|
||||
@@ -54,8 +134,7 @@ type ISCSITargetDriver struct {
|
||||
SCSI *scsi.SCSITargetService
|
||||
Name string
|
||||
iSCSITargets map[string]*ISCSITarget
|
||||
TSIHPool map[uint16]bool
|
||||
TSIHPoolMutex sync.Mutex
|
||||
tsihBitmap *tsihBitmap
|
||||
isClientConnected bool
|
||||
enableStats bool
|
||||
mu *sync.RWMutex
|
||||
@@ -76,7 +155,7 @@ func NewISCSITargetDriver(base *scsi.SCSITargetService) (scsi.SCSITargetDriver,
|
||||
Name: iSCSIDriverName,
|
||||
iSCSITargets: map[string]*ISCSITarget{},
|
||||
SCSI: base,
|
||||
TSIHPool: map[uint16]bool{0: true, 65535: true},
|
||||
tsihBitmap: newTSIHBitmap(),
|
||||
mu: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
@@ -88,24 +167,11 @@ func NewISCSITargetDriver(base *scsi.SCSITargetService) (scsi.SCSITargetDriver,
|
||||
}
|
||||
|
||||
func (s *ISCSITargetDriver) AllocTSIH() uint16 {
|
||||
var i uint16
|
||||
s.TSIHPoolMutex.Lock()
|
||||
for i = uint16(0); i < ISCSI_MAX_TSIH; i++ {
|
||||
exist := s.TSIHPool[i]
|
||||
if !exist {
|
||||
s.TSIHPool[i] = true
|
||||
s.TSIHPoolMutex.Unlock()
|
||||
return i
|
||||
}
|
||||
}
|
||||
s.TSIHPoolMutex.Unlock()
|
||||
return ISCSI_UNSPEC_TSIH
|
||||
return s.tsihBitmap.alloc()
|
||||
}
|
||||
|
||||
func (s *ISCSITargetDriver) ReleaseTSIH(tsih uint16) {
|
||||
s.TSIHPoolMutex.Lock()
|
||||
delete(s.TSIHPool, tsih)
|
||||
s.TSIHPoolMutex.Unlock()
|
||||
s.tsihBitmap.release(tsih)
|
||||
}
|
||||
|
||||
func (s *ISCSITargetDriver) NewTarget(tgtName string, configInfo *config.Config) error {
|
||||
@@ -122,9 +188,9 @@ func (s *ISCSITargetDriver) NewTarget(tgtName string, configInfo *config.Config)
|
||||
targetConfig := configInfo.ISCSITargets[tgtName]
|
||||
for tpgt, portalIDArrary := range targetConfig.TPGTs {
|
||||
tpgtNumber, _ := strconv.ParseUint(tpgt, 10, 16)
|
||||
tgt.TPGTs[uint16(tpgtNumber)] = &iSCSITPGT{uint16(tpgtNumber), make(map[string]struct{})}
|
||||
tgt.TPGTs[uint16(tpgtNumber)] = &iSCSITPGT{TPGT: uint16(tpgtNumber), Portals: make(map[string]struct{})}
|
||||
targetPortName := fmt.Sprintf("%s,t,0x%02x", tgtName, tpgtNumber)
|
||||
scsiTPG.TargetPortGroup = append(scsiTPG.TargetPortGroup, &api.SCSITargetPort{uint16(tpgtNumber), targetPortName})
|
||||
scsiTPG.TargetPortGroup = append(scsiTPG.TargetPortGroup, &api.SCSITargetPort{RelativeTargetPortID: uint16(tpgtNumber), TargetPortName: targetPortName})
|
||||
for _, portalID := range portalIDArrary {
|
||||
portal := configInfo.ISCSIPortals[portalID]
|
||||
s.AddiSCSIPortal(tgtName, uint16(tpgtNumber), portal.Portal)
|
||||
@@ -323,10 +389,12 @@ func (s *ISCSITargetDriver) rxHandler(conn *iscsiConnection) {
|
||||
ddigest uint = 0
|
||||
final bool = false
|
||||
cmd *ISCSICommand
|
||||
buf []byte = make([]byte, BHS_SIZE)
|
||||
buf []byte = getBuffer()
|
||||
length int
|
||||
err error
|
||||
)
|
||||
defer putBuffer(buf)
|
||||
|
||||
conn.readLock.Lock()
|
||||
defer conn.readLock.Unlock()
|
||||
if conn.state == CONN_STATE_SCSI {
|
||||
@@ -366,10 +434,10 @@ func (s *ISCSITargetDriver) rxHandler(conn *iscsiConnection) {
|
||||
}
|
||||
final = true
|
||||
case IOSTATE_RX_INIT_AHS:
|
||||
conn.rxIOState = IOSTATE_RX_DATA
|
||||
break
|
||||
if hdigest != 0 {
|
||||
conn.rxIOState = IOSTATE_RX_INIT_HDIGEST
|
||||
} else {
|
||||
conn.rxIOState = IOSTATE_RX_DATA
|
||||
}
|
||||
case IOSTATE_RX_DATA:
|
||||
if ddigest != 0 {
|
||||
@@ -563,6 +631,92 @@ func iscsiExecNoopOut(conn *iscsiConnection) error {
|
||||
return conn.buildRespPackage(OpNoopIn, nil)
|
||||
}
|
||||
|
||||
// SNACK Type constants per RFC 7143
|
||||
const (
|
||||
SNACK_TYPE_DATA_ACK = 0 // Data ACK
|
||||
SNACK_TYPE_STATUS_ACK = 1 // Status ACK
|
||||
SNACK_TYPE_DATA_R2T = 2 // Data R2T
|
||||
SNACK_TYPE_R_DATA = 3 // R-Data
|
||||
)
|
||||
|
||||
/*
|
||||
* iscsiExecSNACK handles SNACK (Sequence Number Acknowledgement) requests
|
||||
* SNACK is used for error recovery in iSCSI protocol per RFC 7143 section 11.9
|
||||
*/
|
||||
func (s *ISCSITargetDriver) iscsiExecSNACK(conn *iscsiConnection) error {
|
||||
req := conn.req
|
||||
// Parse SNACK type from byte 1, bits 0-1
|
||||
snackType := (req.SCSIOpCode >> 0) & 0x03
|
||||
// Parse BegRun and RunLength from the header
|
||||
begRun := req.ReferencedTaskTag
|
||||
runLength := req.R2TSN
|
||||
|
||||
log.Debugf("SNACK request type=%d, BegRun=%d, RunLength=%d", snackType, begRun, runLength)
|
||||
|
||||
switch snackType {
|
||||
case SNACK_TYPE_DATA_ACK:
|
||||
// Data ACK - initiator acknowledges receipt of Data-In PDUs
|
||||
// For ErrorRecoveryLevel >= 1, we could track acknowledged Data-In
|
||||
log.Debug("SNACK Data ACK received")
|
||||
// Simply return success for now
|
||||
conn.resp = &ISCSICommand{
|
||||
OpCode: OpNoopIn,
|
||||
Final: true,
|
||||
TaskTag: req.TaskTag,
|
||||
StatSN: conn.statSN,
|
||||
ExpCmdSN: conn.expCmdSN,
|
||||
}
|
||||
if conn.session != nil {
|
||||
conn.resp.MaxCmdSN = conn.session.ExpCmdSN + conn.session.MaxQueueCommand
|
||||
}
|
||||
return nil
|
||||
|
||||
case SNACK_TYPE_STATUS_ACK:
|
||||
// Status ACK - initiator acknowledges receipt of status
|
||||
log.Debug("SNACK Status ACK received")
|
||||
// Similar to Data ACK, just acknowledge
|
||||
conn.resp = &ISCSICommand{
|
||||
OpCode: OpNoopIn,
|
||||
Final: true,
|
||||
TaskTag: req.TaskTag,
|
||||
StatSN: conn.statSN,
|
||||
ExpCmdSN: conn.expCmdSN,
|
||||
}
|
||||
if conn.session != nil {
|
||||
conn.resp.MaxCmdSN = conn.session.ExpCmdSN + conn.session.MaxQueueCommand
|
||||
}
|
||||
return nil
|
||||
|
||||
case SNACK_TYPE_DATA_R2T:
|
||||
// Data R2T - request retransmission of R2T
|
||||
log.Debug("SNACK Data R2T received - requesting R2T retransmission")
|
||||
// Find the task and resend R2T
|
||||
conn.session.PendingTasksMutex.RLock()
|
||||
task := conn.session.PendingTasks.GetByTag(begRun)
|
||||
conn.session.PendingTasksMutex.RUnlock()
|
||||
if task == nil {
|
||||
log.Errorf("Cannot find task for R2T retransmission, tag=%d", begRun)
|
||||
return fmt.Errorf("task not found")
|
||||
}
|
||||
// Reset R2T state and resend
|
||||
task.r2tSN = runLength
|
||||
conn.rxTask = task
|
||||
return iscsiExecR2T(conn)
|
||||
|
||||
case SNACK_TYPE_R_DATA:
|
||||
// R-Data - request retransmission of Data-In
|
||||
log.Debug("SNACK R-Data received - requesting Data-In retransmission")
|
||||
// For now, reject this as it requires complex data buffering
|
||||
// In a full implementation, we would need to buffer Data-In PDUs
|
||||
// and retransmit based on BegRun and RunLength
|
||||
log.Warn("R-Data SNACK not fully implemented")
|
||||
return fmt.Errorf("R-Data SNACK not supported")
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown SNACK type: %d", snackType)
|
||||
}
|
||||
}
|
||||
|
||||
func iscsiExecReject(conn *iscsiConnection) error {
|
||||
return conn.buildRespPackage(OpReject, nil)
|
||||
}
|
||||
@@ -852,10 +1006,16 @@ func (s *ISCSITargetDriver) scsiCommandHandler(conn *iscsiConnection) (err error
|
||||
conn.txTask = &iscsiTask{conn: conn, cmd: conn.req, tag: conn.req.TaskTag}
|
||||
conn.txIOState = IOSTATE_TX_BHS
|
||||
iscsiExecLogout(conn)
|
||||
case OpTextReq, OpSNACKReq:
|
||||
case OpTextReq:
|
||||
err = fmt.Errorf("Cannot handle yet %s", opCodeMap[conn.req.OpCode])
|
||||
log.Error(err)
|
||||
return
|
||||
case OpSNACKReq:
|
||||
log.Debug("SNACK Request processing...")
|
||||
if err := s.iscsiExecSNACK(conn); err != nil {
|
||||
log.Errorf("SNACK handling failed: %v", err)
|
||||
iscsiExecReject(conn)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("Unknown op %s", opCodeMap[conn.req.OpCode])
|
||||
log.Error(err)
|
||||
@@ -900,22 +1060,20 @@ func (s *ISCSITargetDriver) iscsiTaskQueueHandler(task *iscsiTask) error {
|
||||
task.state = taskSCSI
|
||||
sess.PendingTasksMutex.Unlock()
|
||||
goto retry
|
||||
} else {
|
||||
if cmd.CmdSN < sess.ExpCmdSN {
|
||||
err := fmt.Errorf("unexpected cmd serial number: (%d, %d)", cmd.CmdSN, sess.ExpCmdSN)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
log.Debugf("add task(%d) into task queue", task.cmd.CmdSN)
|
||||
// add this task into queue and set it as a pending task
|
||||
sess.PendingTasksMutex.Lock()
|
||||
task.state = taskPending
|
||||
sess.PendingTasks.Push(task)
|
||||
sess.PendingTasksMutex.Unlock()
|
||||
return fmt.Errorf("pending")
|
||||
}
|
||||
|
||||
return nil
|
||||
// cmd.CmdSN != sess.ExpCmdSN
|
||||
if cmd.CmdSN < sess.ExpCmdSN {
|
||||
err := fmt.Errorf("unexpected cmd serial number: (%d, %d)", cmd.CmdSN, sess.ExpCmdSN)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
log.Debugf("add task(%d) into task queue", task.cmd.CmdSN)
|
||||
// add this task into queue and set it as a pending task
|
||||
sess.PendingTasksMutex.Lock()
|
||||
task.state = taskPending
|
||||
sess.PendingTasks.Push(task)
|
||||
sess.PendingTasksMutex.Unlock()
|
||||
return fmt.Errorf("pending")
|
||||
}
|
||||
|
||||
func (s *ISCSITargetDriver) iscsiExecTask(task *iscsiTask) error {
|
||||
@@ -972,6 +1130,63 @@ func (s *ISCSITargetDriver) iscsiExecTask(task *iscsiTask) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Async Event types per RFC 7143
|
||||
const (
|
||||
ASYNC_EVENT_SCSI = 0 // SCSI Asynchronous Event
|
||||
ASYNC_EVENT_STATUS = 1 // iSCSI Status Update
|
||||
ASYNC_EVENT_LOGOUT = 2 // iSCSI Logout Request
|
||||
ASYNC_EVENT_DROP_CONN = 3 // iSCSI Drop Connection
|
||||
ASYNC_EVENT_DROP_SESS = 4 // iSCSI Drop All Connections
|
||||
ASYNC_EVENT_NOP = 5 // iSCSI NOP
|
||||
ASYNC_EVENT_VENDOR = 255 // Vendor Specific Event
|
||||
)
|
||||
|
||||
/*
|
||||
* SendAsyncMessage sends an asynchronous message to the initiator
|
||||
* This implements RFC 7143 section 11.10 Asynchronous Message
|
||||
*/
|
||||
func (s *ISCSITargetDriver) SendAsyncMessage(conn *iscsiConnection, eventType byte, lun [8]uint8, param1, param2 uint32, data []byte) error {
|
||||
if conn == nil || conn.state != CONN_STATE_SCSI {
|
||||
return fmt.Errorf("connection not ready for async message")
|
||||
}
|
||||
|
||||
conn.statSN += 1
|
||||
conn.resp = &ISCSICommand{
|
||||
OpCode: OpAsync,
|
||||
SCSIOpCode: eventType,
|
||||
Final: true,
|
||||
LUN: lun,
|
||||
StatSN: conn.statSN,
|
||||
ExpCmdSN: conn.expCmdSN,
|
||||
RawData: data,
|
||||
}
|
||||
if conn.session != nil {
|
||||
conn.resp.MaxCmdSN = conn.session.ExpCmdSN + conn.session.MaxQueueCommand
|
||||
}
|
||||
|
||||
// Parameter1 and Parameter2 are encoded in RawData or could be stored in ISCSICommand
|
||||
// For simplicity, we encode them at the start of RawData if not already present
|
||||
if len(data) == 0 && (param1 != 0 || param2 != 0) {
|
||||
conn.resp.RawData = make([]byte, 8)
|
||||
copy(conn.resp.RawData[0:4], util.MarshalUint32(param1))
|
||||
copy(conn.resp.RawData[4:8], util.MarshalUint32(param2))
|
||||
}
|
||||
|
||||
log.Debugf("Sending Async message type=%d to initiator", eventType)
|
||||
s.handler(DATAOUT, conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendSCSIAsyncEvent sends a SCSI asynchronous event (e.g., LUN reset, storage change)
|
||||
func (s *ISCSITargetDriver) SendSCSIAsyncEvent(conn *iscsiConnection, lun [8]uint8, eventCode byte) error {
|
||||
// SCSI Async Event data format:
|
||||
// bytes 0-1: Event Code
|
||||
// bytes 2-3: Reserved
|
||||
// bytes 4+: Event-specific data
|
||||
data := []byte{eventCode, 0, 0, 0}
|
||||
return s.SendAsyncMessage(conn, ASYNC_EVENT_SCSI, lun, 0, 0, data)
|
||||
}
|
||||
|
||||
func (s *ISCSITargetDriver) Stats() scsi.Stats {
|
||||
s.mu.RLock()
|
||||
stats := s.TargetStats
|
||||
|
||||
176
pkg/port/iscsit/iscsid_test.go
Normal file
176
pkg/port/iscsit/iscsid_test.go
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
Copyright 2024 The GoStor Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package iscsit
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTSIHBitmapAllocRelease(t *testing.T) {
|
||||
b := newTSIHBitmap()
|
||||
|
||||
// Test basic allocation and release
|
||||
tsih1 := b.alloc()
|
||||
if tsih1 == ISCSI_UNSPEC_TSIH {
|
||||
t.Fatal("failed to allocate first TSIH")
|
||||
}
|
||||
if tsih1 != 1 {
|
||||
t.Errorf("expected first TSIH to be 1, got %d", tsih1)
|
||||
}
|
||||
|
||||
tsih2 := b.alloc()
|
||||
if tsih2 == ISCSI_UNSPEC_TSIH {
|
||||
t.Fatal("failed to allocate second TSIH")
|
||||
}
|
||||
if tsih2 != 2 {
|
||||
t.Errorf("expected second TSIH to be 2, got %d", tsih2)
|
||||
}
|
||||
|
||||
// Release first
|
||||
b.release(tsih1)
|
||||
// Note: TSIH bitmap uses circular allocation, next pointer won't return to released positions
|
||||
// This is to avoid concurrency issues, subsequent allocations continue from current next
|
||||
tsih3 := b.alloc()
|
||||
if tsih3 == ISCSI_UNSPEC_TSIH {
|
||||
t.Error("failed to allocate after release")
|
||||
}
|
||||
// Verify tsih1 can be reallocated (at some point)
|
||||
if tsih3 == tsih1 || tsih3 == tsih2 {
|
||||
t.Logf("TSIH was recycled immediately: released %d, got %d", tsih1, tsih3)
|
||||
}
|
||||
|
||||
// Release all
|
||||
b.release(tsih2)
|
||||
b.release(tsih3)
|
||||
}
|
||||
|
||||
func TestTSIHBitmapReservedValues(t *testing.T) {
|
||||
b := newTSIHBitmap()
|
||||
|
||||
// Test reserved values cannot be allocated
|
||||
// 0 and 65535 are reserved values
|
||||
for i := 0; i < 10; i++ {
|
||||
tsih := b.alloc()
|
||||
if tsih == 0 {
|
||||
t.Error("allocated reserved TSIH 0")
|
||||
}
|
||||
if tsih == ISCSI_MAX_TSIH {
|
||||
t.Error("allocated reserved TSIH 65535")
|
||||
}
|
||||
if tsih == ISCSI_UNSPEC_TSIH {
|
||||
break
|
||||
}
|
||||
b.release(tsih)
|
||||
}
|
||||
|
||||
// Test releasing reserved values doesn't panic
|
||||
b.release(0)
|
||||
b.release(ISCSI_MAX_TSIH)
|
||||
}
|
||||
|
||||
func TestTSIHBitmapExhaustion(t *testing.T) {
|
||||
b := newTSIHBitmap()
|
||||
|
||||
// Allocate many TSIHs
|
||||
allocated := make([]uint16, 0, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
tsih := b.alloc()
|
||||
if tsih == ISCSI_UNSPEC_TSIH {
|
||||
t.Fatalf("failed to allocate TSIH at iteration %d", i)
|
||||
}
|
||||
allocated = append(allocated, tsih)
|
||||
}
|
||||
|
||||
// 释放所有
|
||||
for _, tsih := range allocated {
|
||||
b.release(tsih)
|
||||
}
|
||||
|
||||
// Reallocate, should succeed
|
||||
for i := 0; i < 100; i++ {
|
||||
tsih := b.alloc()
|
||||
if tsih == ISCSI_UNSPEC_TSIH {
|
||||
t.Fatalf("failed to reallocate TSIH at iteration %d", i)
|
||||
}
|
||||
b.release(tsih)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTSIHBitmapConcurrency(t *testing.T) {
|
||||
b := newTSIHBitmap()
|
||||
const numGoroutines = 100
|
||||
const allocsPerGoroutine = 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numGoroutines)
|
||||
|
||||
allTSIHs := make(chan uint16, numGoroutines*allocsPerGoroutine)
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < allocsPerGoroutine; j++ {
|
||||
tsih := b.alloc()
|
||||
if tsih != ISCSI_UNSPEC_TSIH {
|
||||
allTSIHs <- tsih
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(allTSIHs)
|
||||
|
||||
// Check no duplicate TSIHs
|
||||
seen := make(map[uint16]bool)
|
||||
for tsih := range allTSIHs {
|
||||
if seen[tsih] {
|
||||
t.Errorf("TSIH %d was allocated more than once", tsih)
|
||||
}
|
||||
seen[tsih] = true
|
||||
}
|
||||
|
||||
// Release all allocated TSIHs
|
||||
for tsih := range seen {
|
||||
b.release(tsih)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTSIHBitmapAlloc(b *testing.B) {
|
||||
bitmap := newTSIHBitmap()
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
tsih := bitmap.alloc()
|
||||
if tsih != ISCSI_UNSPEC_TSIH {
|
||||
bitmap.release(tsih)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkTSIHBitmapAllocSequential(b *testing.B) {
|
||||
bitmap := newTSIHBitmap()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tsih := bitmap.alloc()
|
||||
if tsih != ISCSI_UNSPEC_TSIH {
|
||||
bitmap.release(tsih)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ type iSCSITPGT struct {
|
||||
}
|
||||
|
||||
type ISCSITarget struct {
|
||||
api.SCSITarget
|
||||
*api.SCSITarget
|
||||
api.SCSITargetDriverCommon
|
||||
// TPGT number is the key
|
||||
TPGTs map[uint16]*iSCSITPGT
|
||||
@@ -123,7 +123,7 @@ func (tgt *ISCSITarget) FindTPG(portal string) (uint16, error) {
|
||||
|
||||
func newISCSITarget(target *api.SCSITarget) *ISCSITarget {
|
||||
return &ISCSITarget{
|
||||
SCSITarget: *target,
|
||||
SCSITarget: target,
|
||||
TPGTs: make(map[uint16]*iSCSITPGT),
|
||||
Sessions: make(map[uint16]*ISCSISession),
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package iscsit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -26,20 +25,20 @@ import (
|
||||
|
||||
var (
|
||||
iSCSILoginParamTextKV = []util.KeyValue{
|
||||
{"HeaderDigest", "None"},
|
||||
{"DataDigest", "None"},
|
||||
{"ImmediateData", "Yes"},
|
||||
{"InitialR2T", "Yes"},
|
||||
{"MaxBurstLength", "262144"},
|
||||
{"FirstBurstLength", "65536"},
|
||||
{"MaxRecvDataSegmentLength", "65536"},
|
||||
{"DefaultTime2Wait", "2"},
|
||||
{"DefaultTime2Retain", "0"},
|
||||
{"MaxOutstandingR2T", "1"},
|
||||
{"IFMarker", "No"},
|
||||
{"OFMarker", "No"},
|
||||
{"DataPDUInOrder", "Yes"},
|
||||
{"DataSequenceInOrder", "Yes"}}
|
||||
{Key: "HeaderDigest", Value: "None"},
|
||||
{Key: "DataDigest", Value: "None"},
|
||||
{Key: "ImmediateData", Value: "Yes"},
|
||||
{Key: "InitialR2T", Value: "Yes"},
|
||||
{Key: "MaxBurstLength", Value: "262144"},
|
||||
{Key: "FirstBurstLength", Value: "65536"},
|
||||
{Key: "MaxRecvDataSegmentLength", Value: "65536"},
|
||||
{Key: "DefaultTime2Wait", Value: "2"},
|
||||
{Key: "DefaultTime2Retain", Value: "0"},
|
||||
{Key: "MaxOutstandingR2T", Value: "1"},
|
||||
{Key: "IFMarker", Value: "No"},
|
||||
{Key: "OFMarker", Value: "No"},
|
||||
{Key: "DataPDUInOrder", Value: "Yes"},
|
||||
{Key: "DataSequenceInOrder", Value: "Yes"}}
|
||||
)
|
||||
|
||||
type iSCSILoginStage int
|
||||
@@ -63,10 +62,10 @@ func (s iSCSILoginStage) String() string {
|
||||
}
|
||||
|
||||
func loginKVDeclare(conn *iscsiConnection, negoKV []util.KeyValue) []util.KeyValue {
|
||||
negoKV = append(negoKV, util.KeyValue{"TargetPortalGroupTag",
|
||||
numberKeyInConv(uint(conn.loginParam.tpgt))})
|
||||
negoKV = append(negoKV, util.KeyValue{"MaxRecvDataSegmentLength",
|
||||
numberKeyInConv(sessionKeys["MaxRecvDataSegmentLength"].def)})
|
||||
negoKV = append(negoKV, util.KeyValue{Key: "TargetPortalGroupTag",
|
||||
Value: numberKeyInConv(uint(conn.loginParam.tpgt))})
|
||||
negoKV = append(negoKV, util.KeyValue{Key: "MaxRecvDataSegmentLength",
|
||||
Value: numberKeyInConv(sessionKeys["MaxRecvDataSegmentLength"].def)})
|
||||
return negoKV
|
||||
}
|
||||
|
||||
@@ -158,14 +157,14 @@ func (conn *iscsiConnection) processLoginData() ([]util.KeyValue, error) {
|
||||
if uintVal != defSessKey.def {
|
||||
kvChanges++
|
||||
}
|
||||
negoKV = append(negoKV, util.KeyValue{key, defSessKey.inConv(defSessKey.def)})
|
||||
negoKV = append(negoKV, util.KeyValue{Key: key, Value: defSessKey.inConv(defSessKey.def)})
|
||||
} else {
|
||||
if (uintVal >= defSessKey.min) && (uintVal <= defSessKey.max) {
|
||||
conn.loginParam.sessionParam[defSessKey.idx].Value = uintVal
|
||||
negoKV = append(negoKV, util.KeyValue{key, defSessKey.inConv(uintVal)})
|
||||
negoKV = append(negoKV, util.KeyValue{Key: key, Value: defSessKey.inConv(uintVal)})
|
||||
} else {
|
||||
// the value out of the acceptable range, Uses target default key
|
||||
negoKV = append(negoKV, util.KeyValue{key, defSessKey.inConv(defSessKey.def)})
|
||||
negoKV = append(negoKV, util.KeyValue{Key: key, Value: defSessKey.inConv(defSessKey.def)})
|
||||
kvChanges++
|
||||
}
|
||||
}
|
||||
@@ -222,10 +221,13 @@ type iscsiLoginParam struct {
|
||||
}
|
||||
|
||||
func (m *ISCSICommand) loginRespBytes() []byte {
|
||||
// rfc7143 11.13
|
||||
buf := &bytes.Buffer{}
|
||||
// byte 0
|
||||
buf.WriteByte(byte(OpLoginResp))
|
||||
// rfc7143 11.13 - BHS 48 bytes + data (4-byte aligned)
|
||||
rawDataLen := len(m.RawData)
|
||||
padding := (4 - rawDataLen%4) % 4
|
||||
buf := make([]byte, 48+rawDataLen+padding)
|
||||
|
||||
// byte 0: Opcode
|
||||
buf[0] = byte(OpLoginResp)
|
||||
var b byte
|
||||
if m.Transit {
|
||||
b |= 0x80
|
||||
@@ -236,33 +238,38 @@ func (m *ISCSICommand) loginRespBytes() []byte {
|
||||
b |= byte(m.CSG&0xff) << 2
|
||||
b |= byte(m.NSG & 0xff)
|
||||
// byte 1
|
||||
buf.WriteByte(b)
|
||||
buf[1] = b
|
||||
|
||||
b = 0
|
||||
buf.WriteByte(b) // version-max
|
||||
buf.WriteByte(b) // version-active
|
||||
buf.WriteByte(b) // ahsLen
|
||||
buf.Write(util.MarshalUint64(uint64(len(m.RawData)))[5:]) // data segment length, no padding
|
||||
buf.Write(util.MarshalUint64(m.ISID)[2:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.TSIH))[6:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
|
||||
buf.WriteByte(b)
|
||||
buf.WriteByte(b)
|
||||
buf.WriteByte(b)
|
||||
buf.WriteByte(b) // "reserved"
|
||||
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
|
||||
buf.WriteByte(byte(m.StatusClass))
|
||||
buf.WriteByte(byte(m.StatusDetail))
|
||||
buf.WriteByte(b)
|
||||
buf.WriteByte(b) // "reserved"
|
||||
var bs [8]byte
|
||||
buf.Write(bs[:])
|
||||
rd := m.RawData
|
||||
for len(rd)%4 != 0 {
|
||||
rd = append(rd, 0)
|
||||
}
|
||||
buf.Write(rd)
|
||||
return buf.Bytes()
|
||||
// byte 2: version-max, byte 3: version-active
|
||||
// bytes 4-7: data segment length (24-bit)
|
||||
buf[5] = byte(rawDataLen >> 16)
|
||||
buf[6] = byte(rawDataLen >> 8)
|
||||
buf[7] = byte(rawDataLen)
|
||||
// bytes 8-13: ISID (6 bytes) - lower 6 bytes of uint64
|
||||
buf[8] = byte(m.ISID >> 40)
|
||||
buf[9] = byte(m.ISID >> 32)
|
||||
buf[10] = byte(m.ISID >> 24)
|
||||
buf[11] = byte(m.ISID >> 16)
|
||||
buf[12] = byte(m.ISID >> 8)
|
||||
buf[13] = byte(m.ISID)
|
||||
// bytes 14-15: TSIH (2 bytes)
|
||||
buf[14] = byte(m.TSIH >> 8)
|
||||
buf[15] = byte(m.TSIH)
|
||||
// bytes 16-19: TaskTag
|
||||
util.MarshalUint32To(buf[16:], m.TaskTag)
|
||||
// bytes 20-23: reserved
|
||||
// bytes 24-27: StatSN
|
||||
util.MarshalUint32To(buf[24:], m.StatSN)
|
||||
// bytes 28-31: ExpCmdSN
|
||||
util.MarshalUint32To(buf[28:], m.ExpCmdSN)
|
||||
// bytes 32-35: MaxCmdSN
|
||||
util.MarshalUint32To(buf[32:], m.MaxCmdSN)
|
||||
// bytes 36: StatusClass, 37: StatusDetail
|
||||
buf[36] = byte(m.StatusClass)
|
||||
buf[37] = byte(m.StatusDetail)
|
||||
// bytes 38-47: reserved
|
||||
// Copy data
|
||||
copy(buf[48:], m.RawData)
|
||||
// padding bytes are already zero
|
||||
return buf
|
||||
}
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
package iscsit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gostor/gotgt/pkg/util"
|
||||
)
|
||||
|
||||
func (m *ISCSICommand) logoutRespBytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteByte(byte(OpLogoutResp))
|
||||
buf.WriteByte(0x80)
|
||||
buf.WriteByte(0x00) // response
|
||||
buf.WriteByte(0x00)
|
||||
for i := 4; i < 16; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.TaskTag))[4:])
|
||||
for i := 20; i < 24; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
buf.Write(util.MarshalUint64(uint64(m.StatSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.ExpCmdSN))[4:])
|
||||
buf.Write(util.MarshalUint64(uint64(m.MaxCmdSN))[4:])
|
||||
for i := 36; i < 48; i++ {
|
||||
buf.WriteByte(0x00)
|
||||
}
|
||||
return buf.Bytes()
|
||||
// rfc7143 11.10 - Fixed 48 bytes
|
||||
buf := make([]byte, 48)
|
||||
buf[0] = byte(OpLogoutResp)
|
||||
buf[1] = 0x80
|
||||
// buf[2] = response (0)
|
||||
// bytes 4-15 are reserved (0)
|
||||
// bytes 16-19: TaskTag
|
||||
util.MarshalUint32To(buf[16:], m.TaskTag)
|
||||
// bytes 20-23 are reserved (0)
|
||||
// bytes 24-27: StatSN
|
||||
util.MarshalUint32To(buf[24:], m.StatSN)
|
||||
// bytes 28-31: ExpCmdSN
|
||||
util.MarshalUint32To(buf[28:], m.ExpCmdSN)
|
||||
// bytes 32-35: MaxCmdSN
|
||||
util.MarshalUint32To(buf[32:], m.MaxCmdSN)
|
||||
// bytes 36-47 are reserved (0)
|
||||
return buf
|
||||
}
|
||||
|
||||
403
pkg/port/iscsit/perf_test.go
Normal file
403
pkg/port/iscsit/perf_test.go
Normal file
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
Copyright 2024 The GoStor Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package iscsit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
"github.com/gostor/gotgt/pkg/util"
|
||||
)
|
||||
|
||||
// BenchmarkParseHeader benchmarks iSCSI protocol header parsing performance
|
||||
func BenchmarkParseHeader(b *testing.B) {
|
||||
// Build a typical SCSI CDB command header
|
||||
header := make([]byte, BHS_SIZE)
|
||||
header[0] = byte(OpSCSICmd) // SCSI Command
|
||||
header[1] = 0x80 // Final bit
|
||||
header[4] = 0 // AHS length
|
||||
header[5] = 0
|
||||
header[6] = 0
|
||||
header[7] = 0 // Data segment length = 0
|
||||
// TaskTag at bytes 16-19
|
||||
header[16] = 0x00
|
||||
header[17] = 0x00
|
||||
header[18] = 0x00
|
||||
header[19] = 0x01
|
||||
// ExpectedDataLen at bytes 20-23
|
||||
header[20] = 0x00
|
||||
header[21] = 0x00
|
||||
header[22] = 0x10
|
||||
header[23] = 0x00 // 4096 bytes
|
||||
// CmdSN at bytes 24-27
|
||||
header[24] = 0x00
|
||||
header[25] = 0x00
|
||||
header[26] = 0x00
|
||||
header[27] = 0x01
|
||||
// ExpStatSN at bytes 28-31
|
||||
header[28] = 0x00
|
||||
header[29] = 0x00
|
||||
header[30] = 0x00
|
||||
header[31] = 0x01
|
||||
// CDB at bytes 32-47 (READ_10 command)
|
||||
header[32] = byte(api.READ_10)
|
||||
header[33] = 0x00
|
||||
header[34] = 0x00
|
||||
header[35] = 0x00
|
||||
header[36] = 0x00
|
||||
header[37] = 0x00 // LBA = 0
|
||||
header[38] = 0x00
|
||||
header[39] = 0x08 // Transfer length = 8 blocks
|
||||
header[40] = 0x00 // Control
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cmd, err := parseHeader(header)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = cmd
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseHeaderWithPool benchmarks header parsing with object pool
|
||||
func BenchmarkParseHeaderWithPool(b *testing.B) {
|
||||
header := make([]byte, BHS_SIZE)
|
||||
header[0] = byte(OpSCSICmd)
|
||||
header[1] = 0x80
|
||||
header[32] = byte(api.READ_10)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cmd := getCommand()
|
||||
cmd.OpCode = OpCode(header[0] & ISCSI_OPCODE_MASK)
|
||||
cmd.Final = 0x80&header[1] == 0x80
|
||||
cmd.AHSLen = int(header[4]) * 4
|
||||
cmd.DataLen = int(ParseUint(header[5:8]))
|
||||
cmd.TaskTag = uint32(ParseUint(header[16:20]))
|
||||
cmd.CDB = header[32:48]
|
||||
cmd.StartTime = time.Now()
|
||||
putCommand(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDataInBytes benchmarks Data-In response serialization performance
|
||||
func BenchmarkDataInBytes(b *testing.B) {
|
||||
data := make([]byte, 4096)
|
||||
for i := range data {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpSCSIIn,
|
||||
Final: true,
|
||||
FinalInSeq: true,
|
||||
TaskTag: 1,
|
||||
StatSN: 100,
|
||||
ExpCmdSN: 101,
|
||||
MaxCmdSN: 200,
|
||||
DataLen: 4096,
|
||||
DataSN: 0,
|
||||
BufferOffset: 0,
|
||||
RawData: data,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = cmd.dataInBytes()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDataInBytesSmall benchmarks Data-In performance with small data blocks
|
||||
func BenchmarkDataInBytesSmall(b *testing.B) {
|
||||
data := make([]byte, 512)
|
||||
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpSCSIIn,
|
||||
Final: true,
|
||||
FinalInSeq: true,
|
||||
TaskTag: 1,
|
||||
StatSN: 100,
|
||||
ExpCmdSN: 101,
|
||||
MaxCmdSN: 200,
|
||||
DataLen: 512,
|
||||
DataSN: 0,
|
||||
BufferOffset: 0,
|
||||
RawData: data,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = cmd.dataInBytes()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDataInBytesLarge benchmarks Data-In performance with large data blocks
|
||||
func BenchmarkDataInBytesLarge(b *testing.B) {
|
||||
data := make([]byte, 65536)
|
||||
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpSCSIIn,
|
||||
Final: true,
|
||||
FinalInSeq: true,
|
||||
TaskTag: 1,
|
||||
StatSN: 100,
|
||||
ExpCmdSN: 101,
|
||||
MaxCmdSN: 200,
|
||||
DataLen: 65536,
|
||||
DataSN: 0,
|
||||
BufferOffset: 0,
|
||||
RawData: data,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = cmd.dataInBytes()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkBytesComparison compares Bytes() performance for different OpCodes
|
||||
func BenchmarkBytesComparison(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
cmd *ISCSICommand
|
||||
}{
|
||||
{
|
||||
name: "LoginResp",
|
||||
cmd: &ISCSICommand{
|
||||
OpCode: OpLoginResp,
|
||||
Final: true,
|
||||
Transit: true,
|
||||
CSG: LoginOperationalNegotiation,
|
||||
NSG: FullFeaturePhase,
|
||||
TaskTag: 1,
|
||||
StatSN: 0,
|
||||
ExpCmdSN: 1,
|
||||
MaxCmdSN: 1,
|
||||
StatusClass: 0,
|
||||
StatusDetail: 0,
|
||||
RawData: []byte("TargetPortalGroupTag=1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SCSIResp",
|
||||
cmd: &ISCSICommand{
|
||||
OpCode: OpSCSIResp,
|
||||
Final: true,
|
||||
TaskTag: 1,
|
||||
StatSN: 100,
|
||||
ExpCmdSN: 101,
|
||||
MaxCmdSN: 200,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SCSIIn",
|
||||
cmd: &ISCSICommand{
|
||||
OpCode: OpSCSIIn,
|
||||
Final: true,
|
||||
TaskTag: 1,
|
||||
StatSN: 100,
|
||||
ExpCmdSN: 101,
|
||||
MaxCmdSN: 200,
|
||||
DataLen: 4096,
|
||||
RawData: make([]byte, 4096),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "R2T",
|
||||
cmd: &ISCSICommand{
|
||||
OpCode: OpReady,
|
||||
Final: true,
|
||||
TaskTag: 1,
|
||||
StatSN: 100,
|
||||
ExpCmdSN: 101,
|
||||
MaxCmdSN: 200,
|
||||
R2TSN: 0,
|
||||
BufferOffset: 0,
|
||||
DesiredLength: 8192,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tc.cmd.Bytes()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkCommandPool benchmarks command object pool performance
|
||||
func BenchmarkCommandPool(b *testing.B) {
|
||||
b.Run("WithPool", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cmd := getCommand()
|
||||
cmd.OpCode = OpSCSICmd
|
||||
cmd.TaskTag = uint32(i)
|
||||
putCommand(cmd)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("WithoutPool", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpSCSICmd,
|
||||
TaskTag: uint32(i),
|
||||
}
|
||||
_ = cmd
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkBufferPool benchmarks buffer pool performance
|
||||
func BenchmarkBufferPool(b *testing.B) {
|
||||
b.Run("WithPool", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf := getBuffer()
|
||||
buf[0] = byte(i)
|
||||
putBuffer(buf)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("WithoutPool", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf := make([]byte, BHS_SIZE)
|
||||
buf[0] = byte(i)
|
||||
_ = buf
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkTaskStateTransition benchmarks task state transition performance
|
||||
func BenchmarkTaskStateTransition(b *testing.B) {
|
||||
task := &iscsiTask{
|
||||
tag: 1,
|
||||
state: taskPending,
|
||||
scmd: &api.SCSICommand{},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if i%2 == 0 {
|
||||
task.state = taskPending
|
||||
} else {
|
||||
task.state = taskSCSI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseUint benchmarks ParseUint performance
|
||||
func BenchmarkParseUint(b *testing.B) {
|
||||
testData := []byte{0x00, 0x00, 0x10, 0x00}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ParseUint(testData)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkBytesComparisonEqual benchmarks byte comparison performance
|
||||
func BenchmarkBytesComparisonEqual(b *testing.B) {
|
||||
a := make([]byte, 48)
|
||||
b2 := make([]byte, 48)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = bytes.Equal(a, b2)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMarshalUint32 benchmarks uint32 serialization performance
|
||||
func BenchmarkMarshalUint32(b *testing.B) {
|
||||
val := uint32(0x12345678)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = util.MarshalUint32(val)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMarshalUint64 benchmarks uint64 serialization performance
|
||||
func BenchmarkMarshalUint64(b *testing.B) {
|
||||
val := uint64(0x1234567890ABCDEF)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = util.MarshalUint64(val)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkBuildRespPackage benchmarks complete response package building performance
|
||||
func BenchmarkBuildRespPackage(b *testing.B) {
|
||||
conn := &iscsiConnection{
|
||||
state: CONN_STATE_SCSI,
|
||||
statSN: 99,
|
||||
expCmdSN: 100,
|
||||
loginParam: &iscsiLoginParam{
|
||||
sessionParam: []ISCSISessionParam{
|
||||
{idx: ISCSI_PARAM_MAX_BURST, Value: 262144},
|
||||
},
|
||||
},
|
||||
session: &ISCSISession{
|
||||
ExpCmdSN: 100,
|
||||
MaxQueueCommand: 32,
|
||||
},
|
||||
req: &ISCSICommand{
|
||||
OpCode: OpSCSICmd,
|
||||
TaskTag: 1,
|
||||
ExpStatSN: 100,
|
||||
ExpectedDataLen: 4096,
|
||||
StartTime: time.Now(),
|
||||
},
|
||||
rxTask: &iscsiTask{
|
||||
tag: 1,
|
||||
scmd: &api.SCSICommand{
|
||||
Result: 0,
|
||||
Direction: api.SCSIDataRead,
|
||||
InSDBBuffer: &api.SCSIDataBuffer{
|
||||
Buffer: make([]byte, 4096),
|
||||
Length: 4096,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = conn.buildRespPackage(OpSCSIResp, nil)
|
||||
}
|
||||
}
|
||||
472
pkg/port/iscsit/protocol_test.go
Normal file
472
pkg/port/iscsit/protocol_test.go
Normal file
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
Copyright 2024 The GoStor Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package iscsit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestLoginRespBytesFormat verifies Login Response BHS format complies with RFC 7143
|
||||
func TestLoginRespBytesFormat(t *testing.T) {
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpLoginResp,
|
||||
Transit: true,
|
||||
Cont: false,
|
||||
CSG: LoginOperationalNegotiation,
|
||||
NSG: FullFeaturePhase,
|
||||
ISID: 0x123456789ABC,
|
||||
TSIH: 0x1234,
|
||||
TaskTag: 0xABCDEF00,
|
||||
StatSN: 0x12345678,
|
||||
ExpCmdSN: 0x87654321,
|
||||
MaxCmdSN: 0x87654421,
|
||||
StatusClass: 0,
|
||||
StatusDetail: 0,
|
||||
RawData: []byte("TestData"),
|
||||
}
|
||||
|
||||
resp := cmd.loginRespBytes()
|
||||
|
||||
// Verify BHS length is at least 48 bytes
|
||||
if len(resp) < 48 {
|
||||
t.Fatalf("BHS too short: expected at least 48, got %d", len(resp))
|
||||
}
|
||||
|
||||
// Byte 0: Opcode
|
||||
if resp[0] != byte(OpLoginResp) {
|
||||
t.Errorf("Byte 0: expected OpLoginResp(0x23), got 0x%02x", resp[0])
|
||||
}
|
||||
|
||||
// Byte 1: Flags
|
||||
expectedFlags := byte(0x80 | (byte(LoginOperationalNegotiation&0xff) << 2) | byte(FullFeaturePhase&0xff))
|
||||
if resp[1] != expectedFlags {
|
||||
t.Errorf("Byte 1: expected 0x%02x, got 0x%02x", expectedFlags, resp[1])
|
||||
}
|
||||
|
||||
// Byte 2-3: Version
|
||||
if resp[2] != 0 || resp[3] != 0 {
|
||||
t.Logf("Byte 2-3 (version): %d, %d", resp[2], resp[3])
|
||||
}
|
||||
|
||||
// Byte 4-7: Data Segment Length
|
||||
dataLen := binary.BigEndian.Uint32(resp[4:8])
|
||||
if dataLen != uint32(len(cmd.RawData)) {
|
||||
t.Errorf("Data segment length: expected %d, got %d", len(cmd.RawData), dataLen)
|
||||
}
|
||||
|
||||
// Byte 8-13: ISID (6 bytes)
|
||||
isid := binary.BigEndian.Uint64(append([]byte{0, 0}, resp[8:14]...))
|
||||
if isid != cmd.ISID {
|
||||
t.Errorf("ISID: expected 0x%012x, got 0x%012x", cmd.ISID, isid)
|
||||
}
|
||||
|
||||
// Byte 14-15: TSIH
|
||||
tsih := binary.BigEndian.Uint16(resp[14:16])
|
||||
if tsih != cmd.TSIH {
|
||||
t.Errorf("TSIH: expected 0x%04x, got 0x%04x", cmd.TSIH, tsih)
|
||||
}
|
||||
|
||||
// Byte 16-19: Task Tag
|
||||
taskTag := binary.BigEndian.Uint32(resp[16:20])
|
||||
if taskTag != cmd.TaskTag {
|
||||
t.Errorf("TaskTag: expected 0x%08x, got 0x%08x", cmd.TaskTag, taskTag)
|
||||
}
|
||||
|
||||
// Byte 24-27: StatSN
|
||||
statSN := binary.BigEndian.Uint32(resp[24:28])
|
||||
if statSN != cmd.StatSN {
|
||||
t.Errorf("StatSN: expected 0x%08x, got 0x%08x", cmd.StatSN, statSN)
|
||||
}
|
||||
|
||||
// Byte 28-31: ExpCmdSN
|
||||
expCmdSN := binary.BigEndian.Uint32(resp[28:32])
|
||||
if expCmdSN != cmd.ExpCmdSN {
|
||||
t.Errorf("ExpCmdSN: expected 0x%08x, got 0x%08x", cmd.ExpCmdSN, expCmdSN)
|
||||
}
|
||||
|
||||
// Byte 32-35: MaxCmdSN
|
||||
maxCmdSN := binary.BigEndian.Uint32(resp[32:36])
|
||||
if maxCmdSN != cmd.MaxCmdSN {
|
||||
t.Errorf("MaxCmdSN: expected 0x%08x, got 0x%08x", cmd.MaxCmdSN, maxCmdSN)
|
||||
}
|
||||
|
||||
// Byte 36: StatusClass
|
||||
if resp[36] != cmd.StatusClass {
|
||||
t.Errorf("StatusClass: expected %d, got %d", cmd.StatusClass, resp[36])
|
||||
}
|
||||
|
||||
// Byte 37: StatusDetail
|
||||
if resp[37] != cmd.StatusDetail {
|
||||
t.Errorf("StatusDetail: expected %d, got %d", cmd.StatusDetail, resp[37])
|
||||
}
|
||||
|
||||
// Verify data segment
|
||||
if len(resp) > 48 {
|
||||
data := resp[48:]
|
||||
if !bytes.Equal(data, cmd.RawData) {
|
||||
t.Errorf("RawData mismatch: expected %v, got %v", cmd.RawData, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify 4-byte alignment
|
||||
if len(resp)%4 != 0 {
|
||||
t.Errorf("Response not aligned to 4 bytes: length=%d", len(resp))
|
||||
}
|
||||
}
|
||||
|
||||
// TestLogoutRespBytesFormat verifies Logout Response BHS format
|
||||
func TestLogoutRespBytesFormat(t *testing.T) {
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpLogoutResp,
|
||||
TaskTag: 0x12345678,
|
||||
StatSN: 0xABCDEF00,
|
||||
ExpCmdSN: 0x11223344,
|
||||
MaxCmdSN: 0x55667788,
|
||||
}
|
||||
|
||||
resp := cmd.logoutRespBytes()
|
||||
|
||||
// Verify length is exactly 48 bytes
|
||||
if len(resp) != 48 {
|
||||
t.Fatalf("Logout response length: expected 48, got %d", len(resp))
|
||||
}
|
||||
|
||||
// Byte 0: Opcode
|
||||
if resp[0] != byte(OpLogoutResp) {
|
||||
t.Errorf("Byte 0: expected OpLogoutResp(0x26), got 0x%02x", resp[0])
|
||||
}
|
||||
|
||||
// Byte 1: Flags (0x80)
|
||||
if resp[1] != 0x80 {
|
||||
t.Errorf("Byte 1: expected 0x80, got 0x%02x", resp[1])
|
||||
}
|
||||
|
||||
// Byte 2: Response (0)
|
||||
if resp[2] != 0 {
|
||||
t.Errorf("Byte 2: expected 0, got 0x%02x", resp[2])
|
||||
}
|
||||
|
||||
// Byte 16-19: Task Tag
|
||||
taskTag := binary.BigEndian.Uint32(resp[16:20])
|
||||
if taskTag != cmd.TaskTag {
|
||||
t.Errorf("TaskTag: expected 0x%08x, got 0x%08x", cmd.TaskTag, taskTag)
|
||||
}
|
||||
|
||||
// Byte 24-27: StatSN
|
||||
statSN := binary.BigEndian.Uint32(resp[24:28])
|
||||
if statSN != cmd.StatSN {
|
||||
t.Errorf("StatSN: expected 0x%08x, got 0x%08x", cmd.StatSN, statSN)
|
||||
}
|
||||
|
||||
// Byte 28-31: ExpCmdSN
|
||||
expCmdSN := binary.BigEndian.Uint32(resp[28:32])
|
||||
if expCmdSN != cmd.ExpCmdSN {
|
||||
t.Errorf("ExpCmdSN: expected 0x%08x, got 0x%08x", cmd.ExpCmdSN, expCmdSN)
|
||||
}
|
||||
|
||||
// Byte 32-35: MaxCmdSN
|
||||
maxCmdSN := binary.BigEndian.Uint32(resp[32:36])
|
||||
if maxCmdSN != cmd.MaxCmdSN {
|
||||
t.Errorf("MaxCmdSN: expected 0x%08x, got 0x%08x", cmd.MaxCmdSN, maxCmdSN)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSCSICmdRespBytesFormat verifies SCSI Command Response BHS format
|
||||
func TestSCSICmdRespBytesFormat(t *testing.T) {
|
||||
rawData := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpSCSIResp,
|
||||
Status: 0x00, // GOOD
|
||||
SCSIResponse: 0x00,
|
||||
TaskTag: 0xABCDEF00,
|
||||
StatSN: 0x12345678,
|
||||
ExpCmdSN: 0x87654321,
|
||||
MaxCmdSN: 0x87654421,
|
||||
Resid: 0,
|
||||
RawData: rawData,
|
||||
ExpectedDataLen: uint32(len(rawData)),
|
||||
}
|
||||
|
||||
resp := cmd.scsiCmdRespBytes()
|
||||
|
||||
// Verify length
|
||||
if len(resp) < 48 {
|
||||
t.Fatalf("SCSI response too short: expected at least 48, got %d", len(resp))
|
||||
}
|
||||
|
||||
// Byte 0: Opcode
|
||||
if resp[0] != byte(OpSCSIResp) {
|
||||
t.Errorf("Byte 0: expected OpSCSIResp(0x21), got 0x%02x", resp[0])
|
||||
}
|
||||
|
||||
// Byte 1: Flags (0x80 = final, no residual)
|
||||
if resp[1] != 0x80 {
|
||||
t.Errorf("Byte 1: expected 0x80, got 0x%02x", resp[1])
|
||||
}
|
||||
|
||||
// Byte 2: SCSI Response
|
||||
if resp[2] != 0 {
|
||||
t.Errorf("Byte 2 (SCSI Response): expected 0, got %d", resp[2])
|
||||
}
|
||||
|
||||
// Byte 3: Status
|
||||
if resp[3] != cmd.Status {
|
||||
t.Errorf("Byte 3 (Status): expected %d, got %d", cmd.Status, resp[3])
|
||||
}
|
||||
|
||||
// 验证数据段
|
||||
if len(resp) > 48 {
|
||||
data := resp[48:]
|
||||
if len(data) >= len(rawData) {
|
||||
if !bytes.Equal(data[:len(rawData)], rawData) {
|
||||
t.Errorf("RawData mismatch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify 4-byte alignment
|
||||
if len(resp)%4 != 0 {
|
||||
t.Errorf("Response not aligned to 4 bytes: length=%d", len(resp))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDataInBytesFormat verifies Data-In response format
|
||||
func TestDataInBytesFormat(t *testing.T) {
|
||||
rawData := make([]byte, 512) // Simulate 512 bytes of data
|
||||
for i := range rawData {
|
||||
rawData[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpSCSIIn,
|
||||
Final: true,
|
||||
FinalInSeq: true,
|
||||
HasStatus: true,
|
||||
Status: 0x00,
|
||||
DataLen: len(rawData),
|
||||
TaskTag: 0x12345678,
|
||||
StatSN: 0xABCDEF00,
|
||||
ExpCmdSN: 0x11111111,
|
||||
MaxCmdSN: 0x22222222,
|
||||
DataSN: 0,
|
||||
BufferOffset: 0,
|
||||
Resid: 0,
|
||||
RawData: rawData,
|
||||
ExpectedDataLen: uint32(len(rawData)),
|
||||
SCSIOpCode: 0x28, // READ_10
|
||||
}
|
||||
|
||||
resp := cmd.dataInBytes()
|
||||
|
||||
// 验证长度
|
||||
expectedLen := 48 + len(rawData)
|
||||
if len(rawData)%4 != 0 {
|
||||
expectedLen += 4 - len(rawData)%4
|
||||
}
|
||||
if len(resp) != expectedLen {
|
||||
t.Fatalf("Data-In response length: expected %d, got %d", expectedLen, len(resp))
|
||||
}
|
||||
|
||||
// Byte 0: Opcode
|
||||
if resp[0] != byte(OpSCSIIn) {
|
||||
t.Errorf("Byte 0: expected OpSCSIIn(0x25), got 0x%02x", resp[0])
|
||||
}
|
||||
|
||||
// Byte 1: Flags (0x80 = final, 0x01 = status present)
|
||||
expectedFlags := byte(0x80 | 0x01)
|
||||
if resp[1] != expectedFlags {
|
||||
t.Errorf("Byte 1: expected 0x%02x, got 0x%02x", expectedFlags, resp[1])
|
||||
}
|
||||
|
||||
// Byte 3: Status
|
||||
if resp[3] != cmd.Status {
|
||||
t.Errorf("Byte 3 (Status): expected %d, got %d", cmd.Status, resp[3])
|
||||
}
|
||||
|
||||
// 验证数据段
|
||||
data := resp[48:]
|
||||
if !bytes.Equal(data, rawData) {
|
||||
t.Errorf("Data segment mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// TestR2TRespBytesFormat verifies R2T (Ready To Transfer) response format
|
||||
func TestR2TRespBytesFormat(t *testing.T) {
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpReady,
|
||||
Final: true,
|
||||
TaskTag: 0x12345678,
|
||||
StatSN: 0xABCDEF00,
|
||||
ExpCmdSN: 0x11111111,
|
||||
MaxCmdSN: 0x22222222,
|
||||
R2TSN: 0,
|
||||
BufferOffset: 0,
|
||||
DesiredLength: 8192,
|
||||
}
|
||||
|
||||
resp := cmd.r2tRespBytes()
|
||||
|
||||
// Verify length is exactly 48 bytes
|
||||
if len(resp) != 48 {
|
||||
t.Fatalf("R2T response length: expected 48, got %d", len(resp))
|
||||
}
|
||||
|
||||
// Byte 0: Opcode
|
||||
if resp[0] != byte(OpReady) {
|
||||
t.Errorf("Byte 0: expected OpReady(0x31), got 0x%02x", resp[0])
|
||||
}
|
||||
|
||||
// Byte 1: Flags (0x80 = final)
|
||||
if resp[1] != 0x80 {
|
||||
t.Errorf("Byte 1: expected 0x80, got 0x%02x", resp[1])
|
||||
}
|
||||
|
||||
// Byte 16-19: Task Tag
|
||||
taskTag := binary.BigEndian.Uint32(resp[16:20])
|
||||
if taskTag != cmd.TaskTag {
|
||||
t.Errorf("TaskTag: expected 0x%08x, got 0x%08x", cmd.TaskTag, taskTag)
|
||||
}
|
||||
|
||||
// Byte 36-39: R2TSN
|
||||
r2tsn := binary.BigEndian.Uint32(resp[36:40])
|
||||
if r2tsn != cmd.R2TSN {
|
||||
t.Errorf("R2TSN: expected 0x%08x, got 0x%08x", cmd.R2TSN, r2tsn)
|
||||
}
|
||||
|
||||
// Byte 40-43: Buffer Offset
|
||||
bufferOffset := binary.BigEndian.Uint32(resp[40:44])
|
||||
if bufferOffset != cmd.BufferOffset {
|
||||
t.Errorf("BufferOffset: expected 0x%08x, got 0x%08x", cmd.BufferOffset, bufferOffset)
|
||||
}
|
||||
|
||||
// Byte 44-47: Desired Length
|
||||
desiredLength := binary.BigEndian.Uint32(resp[44:48])
|
||||
if desiredLength != cmd.DesiredLength {
|
||||
t.Errorf("DesiredLength: expected 0x%08x, got 0x%08x", cmd.DesiredLength, desiredLength)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLoginRespBytes benchmarks Login Response
|
||||
func BenchmarkLoginRespBytes(b *testing.B) {
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpLoginResp,
|
||||
Transit: true,
|
||||
ISID: 0x123456789ABC,
|
||||
TSIH: 0x1234,
|
||||
TaskTag: 0xABCDEF00,
|
||||
StatSN: 0x12345678,
|
||||
ExpCmdSN: 0x87654321,
|
||||
MaxCmdSN: 0x87654421,
|
||||
StatusClass: 0,
|
||||
StatusDetail: 0,
|
||||
RawData: []byte("TestData"),
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = cmd.loginRespBytes()
|
||||
}
|
||||
}
|
||||
|
||||
// TestTextRespBytesFormat verifies Text Response BHS format
|
||||
func TestTextRespBytesFormat(t *testing.T) {
|
||||
rawData := []byte("SendTargets=test")
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpTextResp,
|
||||
Final: true,
|
||||
Cont: false,
|
||||
TaskTag: 0x12345678,
|
||||
StatSN: 0xABCDEF00,
|
||||
ExpCmdSN: 0x11111111,
|
||||
MaxCmdSN: 0x22222222,
|
||||
RawData: rawData,
|
||||
}
|
||||
|
||||
resp := cmd.textRespBytes()
|
||||
|
||||
// Verify BHS length is at least 48 bytes
|
||||
if len(resp) < 48 {
|
||||
t.Fatalf("BHS too short: expected at least 48, got %d", len(resp))
|
||||
}
|
||||
|
||||
// Byte 0: Opcode
|
||||
if resp[0] != byte(OpTextResp) {
|
||||
t.Errorf("Byte 0: expected OpTextResp(0x24), got 0x%02x", resp[0])
|
||||
}
|
||||
|
||||
// Byte 1: Flags (0x80 = final)
|
||||
if resp[1] != 0x80 {
|
||||
t.Errorf("Byte 1: expected 0x80, got 0x%02x", resp[1])
|
||||
}
|
||||
|
||||
// Byte 4-7: Data Segment Length
|
||||
dataLen := binary.BigEndian.Uint32(resp[4:8])
|
||||
if dataLen != uint32(len(rawData)) {
|
||||
t.Errorf("Data segment length: expected %d, got %d", len(rawData), dataLen)
|
||||
}
|
||||
|
||||
// Byte 16-19: Task Tag
|
||||
taskTag := binary.BigEndian.Uint32(resp[16:20])
|
||||
if taskTag != cmd.TaskTag {
|
||||
t.Errorf("TaskTag: expected 0x%08x, got 0x%08x", cmd.TaskTag, taskTag)
|
||||
}
|
||||
|
||||
// Byte 20-23: 0xffffffff
|
||||
if resp[20] != 0xff || resp[21] != 0xff || resp[22] != 0xff || resp[23] != 0xff {
|
||||
t.Errorf("Bytes 20-23: expected 0xffffffff, got 0x%02x%02x%02x%02x",
|
||||
resp[20], resp[21], resp[22], resp[23])
|
||||
}
|
||||
|
||||
// Byte 24-27: StatSN
|
||||
statSN := binary.BigEndian.Uint32(resp[24:28])
|
||||
if statSN != cmd.StatSN {
|
||||
t.Errorf("StatSN: expected 0x%08x, got 0x%08x", cmd.StatSN, statSN)
|
||||
}
|
||||
|
||||
// 验证数据段
|
||||
if len(resp) > 48 {
|
||||
data := resp[48:]
|
||||
if !bytes.Equal(data, rawData) {
|
||||
t.Errorf("RawData mismatch: expected %v, got %v", rawData, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify 4-byte alignment
|
||||
if len(resp)%4 != 0 {
|
||||
t.Errorf("Response not aligned to 4 bytes: length=%d", len(resp))
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSCSICmdRespBytes benchmarks SCSI Command Response
|
||||
func BenchmarkSCSICmdRespBytes(b *testing.B) {
|
||||
cmd := &ISCSICommand{
|
||||
OpCode: OpSCSIResp,
|
||||
Status: 0x00,
|
||||
TaskTag: 0xABCDEF00,
|
||||
StatSN: 0x12345678,
|
||||
ExpCmdSN: 0x87654321,
|
||||
MaxCmdSN: 0x87654421,
|
||||
RawData: []byte{0x00, 0x01, 0x02, 0x03},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = cmd.scsiCmdRespBytes()
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
"github.com/gostor/gotgt/pkg/scsi"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -333,7 +333,7 @@ func (s *ISCSITargetDriver) UnBindISCSISession(sess *ISCSISession) {
|
||||
target.SessionsRWMutex.Lock()
|
||||
defer target.SessionsRWMutex.Unlock()
|
||||
delete(target.Sessions, sess.TSIH)
|
||||
scsi.RemoveITNexus(&sess.Target.SCSITarget, sess.ITNexus)
|
||||
scsi.RemoveITNexus(sess.Target.SCSITarget, sess.ITNexus)
|
||||
}
|
||||
|
||||
func (s *ISCSITargetDriver) BindISCSISession(conn *iscsiConnection) error {
|
||||
@@ -395,8 +395,8 @@ func (s *ISCSITargetDriver) BindISCSISession(conn *iscsiConnection) error {
|
||||
log.Infof("Login request received from initiator: %v, Session type: %s, Target name:%v, ISID: 0x%x",
|
||||
conn.loginParam.initiator, "Normal", conn.loginParam.target, conn.loginParam.isid)
|
||||
//register normal session
|
||||
itnexus := &api.ITNexus{uuid.NewV1(), GeniSCSIITNexusID(newSess)}
|
||||
scsi.AddITNexus(&newSess.Target.SCSITarget, itnexus)
|
||||
itnexus := &api.ITNexus{ID: uuid.New(), Tag: GeniSCSIITNexusID(newSess)}
|
||||
scsi.AddITNexus(newSess.Target.SCSITarget, itnexus)
|
||||
newSess.ITNexus = itnexus
|
||||
conn.session = newSess
|
||||
|
||||
@@ -417,8 +417,8 @@ func (s *ISCSITargetDriver) BindISCSISession(conn *iscsiConnection) error {
|
||||
return err
|
||||
}
|
||||
|
||||
itnexus := &api.ITNexus{uuid.NewV1(), GeniSCSIITNexusID(newSess)}
|
||||
scsi.AddITNexus(&newSess.Target.SCSITarget, itnexus)
|
||||
itnexus := &api.ITNexus{ID: uuid.New(), Tag: GeniSCSIITNexusID(newSess)}
|
||||
scsi.AddITNexus(newSess.Target.SCSITarget, itnexus)
|
||||
newSess.ITNexus = itnexus
|
||||
conn.session = newSess
|
||||
|
||||
|
||||
Reference in New Issue
Block a user