support more SCSI commands: ReadDefectData, Sanitize, and expanded CI
New SCSI commands implemented: - READ DEFECT DATA(10/12): returns empty defect list (virtual device) - SANITIZE: supports OVERWRITE and BLOCK ERASE (zeros all blocks) - EXTENDED COPY / RECEIVE COPY RESULTS: registered as unsupported New unit tests for ReadDefectData10/12, Sanitize, and command registration. New CI libiscsi test cases: - PersistentReservation (PrinReadKeys, PrinReportCapabilities, ProutRegister, ProutReserve) - ReadDefectData10/12 (Simple) - CompareAndWrite (Simple) - OrWrite (Simple, BeyondEol, ZeroBlocks) - GetLBAStatus (Simple, BeyondEol) - ReportSupportedOpcodes (OneCommand) Partial fix for #55 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
29
.github/workflows/gotgt.yml
vendored
29
.github/workflows/gotgt.yml
vendored
@@ -106,18 +106,41 @@ jobs:
|
||||
|
||||
# Reserve/Release Tests
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.Reserve6.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
|
||||
# Persistent Reservation Tests
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.PrinReadKeys.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.PrinReportCapabilities.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.ProutRegister.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.ProutReserve.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
# Unmap Tests
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.Unmap.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.Unmap.VPD iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.Unmap.ZeroBlocks iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
|
||||
# Read Defect Data Tests
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.ReadDefectData10.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.ReadDefectData12.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
# CompareAndWrite Tests
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.CompareAndWrite.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
# OrWrite Tests
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.OrWrite.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.OrWrite.BeyondEol iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.OrWrite.ZeroBlocks iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
# GetLBAStatus Tests
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.GetLBAStatus.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.GetLBAStatus.BeyondEol iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
# Other SCSI Tests
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.PreventAllow iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.StartStopUnit iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.TestUnitReady iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.ReportSupportedOpcodes.Simple iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.ReportSupportedOpcodes.OneCommand iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
# iSCSI Protocol Tests
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.iSCSITMF iscsi://127.0.0.1:3260/${{env.TARGET}}/0 || true
|
||||
./test-tool/iscsi-test-cu -d -A --test=ALL.iSCSIcmdsn iscsi://127.0.0.1:3260/${{env.TARGET}}/0
|
||||
|
||||
@@ -83,6 +83,7 @@ var (
|
||||
UNMAP SCSICommandType = 0x42
|
||||
READ_TOC SCSICommandType = 0x43
|
||||
GET_CONFIGURATION SCSICommandType = 0x46
|
||||
SANITIZE SCSICommandType = 0x48
|
||||
LOG_SELECT SCSICommandType = 0x4c
|
||||
LOG_SENSE SCSICommandType = 0x4d
|
||||
READ_DISK_INFO SCSICommandType = 0x51
|
||||
@@ -95,6 +96,8 @@ var (
|
||||
READ_BUFFER_CAP SCSICommandType = 0x5c
|
||||
PERSISTENT_RESERVE_IN SCSICommandType = 0x5e
|
||||
PERSISTENT_RESERVE_OUT SCSICommandType = 0x5f
|
||||
EXTENDED_COPY SCSICommandType = 0x83
|
||||
RECEIVE_COPY_RESULTS SCSICommandType = 0x84
|
||||
VARLEN_CDB SCSICommandType = 0x7f
|
||||
READ_16 SCSICommandType = 0x88
|
||||
COMPARE_AND_WRITE SCSICommandType = 0x89
|
||||
@@ -121,6 +124,7 @@ var (
|
||||
SEARCH_HIGH_12 SCSICommandType = 0xb0
|
||||
SEARCH_EQUAL_12 SCSICommandType = 0xb1
|
||||
SEARCH_LOW_12 SCSICommandType = 0xb2
|
||||
READ_DEFECT_DATA_12 SCSICommandType = 0xb7
|
||||
READ_ELEMENT_STATUS SCSICommandType = 0xb8
|
||||
SEND_VOLUME_TAG SCSICommandType = 0xb6
|
||||
SET_STREAMING SCSICommandType = 0xb6
|
||||
|
||||
104
pkg/scsi/sbc.go
104
pkg/scsi/sbc.go
@@ -176,8 +176,11 @@ func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol {
|
||||
sbc.SCSIDeviceOps[api.PRE_FETCH_10] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
|
||||
sbc.SCSIDeviceOps[api.SYNCHRONIZE_CACHE] = NewSCSIDeviceOperation(SBCSyncCache, nil, PR_WE_FA|PR_EA_FA|PR_WE_FN|PR_EA_FN)
|
||||
|
||||
sbc.SCSIDeviceOps[api.READ_DEFECT_DATA] = NewSCSIDeviceOperation(SBCReadDefectData, nil, 0)
|
||||
|
||||
sbc.SCSIDeviceOps[api.WRITE_SAME] = NewSCSIDeviceOperation(SBCReadWrite, nil, 0)
|
||||
sbc.SCSIDeviceOps[api.UNMAP] = NewSCSIDeviceOperation(SBCUnmap, nil, 0)
|
||||
sbc.SCSIDeviceOps[api.SANITIZE] = NewSCSIDeviceOperation(SBCSanitize, nil, 0)
|
||||
|
||||
sbc.SCSIDeviceOps[api.MODE_SELECT_10] = NewSCSIDeviceOperation(SBCModeSelect, nil, PR_WE_FA|PR_EA_FA|PR_EA_FN|PR_WE_FN)
|
||||
sbc.SCSIDeviceOps[api.MODE_SENSE_10] = NewSCSIDeviceOperation(SBCModeSense, nil, PR_WE_FA|PR_WE_FN|PR_EA_FA|PR_EA_FN)
|
||||
@@ -213,6 +216,8 @@ func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol {
|
||||
sbc.SCSIDeviceOps[api.WRITE_SAME_16] = NewSCSIDeviceOperation(SBCReadWrite, nil, 0)
|
||||
sbc.SCSIDeviceOps[api.SERVICE_ACTION_IN] = NewSCSIDeviceOperation(SBCServiceAction, nil, 0)
|
||||
|
||||
sbc.SCSIDeviceOps[api.EXTENDED_COPY] = NewSCSIDeviceOperation(SPCIllegalOp, nil, 0)
|
||||
sbc.SCSIDeviceOps[api.RECEIVE_COPY_RESULTS] = NewSCSIDeviceOperation(SPCIllegalOp, nil, 0)
|
||||
sbc.SCSIDeviceOps[api.REPORT_LUNS] = NewSCSIDeviceOperation(SPCReportLuns, nil, 0)
|
||||
sbc.SCSIDeviceOps[api.MAINT_PROTOCOL_IN] = NewSCSIDeviceOperation(SPCServiceAction, []*SCSIServiceAction{
|
||||
{ServiceAction: 0x0C, CommandPerformFunc: SPCReportSupportedOperationCodes},
|
||||
@@ -222,6 +227,7 @@ func NewSBCDevice(deviceType api.SCSIDeviceType) api.SCSIDeviceProtocol {
|
||||
sbc.SCSIDeviceOps[api.WRITE_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_WE_FA|PR_EA_FA|PR_WE_FA|PR_WE_FN)
|
||||
sbc.SCSIDeviceOps[api.WRITE_VERIFY_12] = NewSCSIDeviceOperation(SBCReadWrite, nil, PR_EA_FA|PR_EA_FN)
|
||||
sbc.SCSIDeviceOps[api.VERIFY_12] = NewSCSIDeviceOperation(SBCVerify, nil, PR_EA_FA|PR_EA_FN)
|
||||
sbc.SCSIDeviceOps[api.READ_DEFECT_DATA_12] = NewSCSIDeviceOperation(SBCReadDefectData, nil, 0)
|
||||
sbc.SCSIDeviceOps[api.COMPARE_AND_WRITE] = NewSCSIDeviceOperation(SBCCompareAndWrite, nil, PR_EA_FA|PR_EA_FN)
|
||||
|
||||
return sbc
|
||||
@@ -845,3 +851,101 @@ func SBCSyncCache(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
|
||||
return api.SAMStatGood
|
||||
}
|
||||
|
||||
/*
|
||||
* SBCReadDefectData implements SCSI READ DEFECT DATA(10) and READ DEFECT DATA(12) commands
|
||||
* Returns an empty defect list as this is a virtual device with no physical defects.
|
||||
*
|
||||
* Reference : SBC-3
|
||||
* 5.17 - READ DEFECT DATA (10)
|
||||
* 5.18 - READ DEFECT DATA (12)
|
||||
*/
|
||||
func SBCReadDefectData(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
scb := cmd.SCB
|
||||
opcode := api.SCSICommandType(scb[0])
|
||||
|
||||
reqFormat := scb[2] & 0x07
|
||||
plist := scb[2] & 0x10
|
||||
glist := scb[2] & 0x08
|
||||
|
||||
if opcode == api.READ_DEFECT_DATA {
|
||||
// READ DEFECT DATA(10): 4 bytes header
|
||||
if cmd.InSDBBuffer.Length < 4 {
|
||||
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
||||
return api.SAMStatCheckCondition
|
||||
}
|
||||
buf := cmd.InSDBBuffer.Buffer
|
||||
buf[0] = 0
|
||||
buf[1] = (plist | glist | reqFormat)
|
||||
buf[2] = 0
|
||||
buf[3] = 0
|
||||
cmd.InSDBBuffer.Resid = 4
|
||||
} else {
|
||||
// READ DEFECT DATA(12): 8 bytes header
|
||||
if cmd.InSDBBuffer.Length < 8 {
|
||||
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
||||
return api.SAMStatCheckCondition
|
||||
}
|
||||
buf := cmd.InSDBBuffer.Buffer
|
||||
buf[0] = 0
|
||||
buf[1] = (plist | glist | reqFormat)
|
||||
buf[2] = 0
|
||||
buf[3] = 0
|
||||
buf[4] = 0
|
||||
buf[5] = 0
|
||||
buf[6] = 0
|
||||
buf[7] = 0
|
||||
cmd.InSDBBuffer.Resid = 8
|
||||
}
|
||||
|
||||
return api.SAMStatGood
|
||||
}
|
||||
|
||||
/*
|
||||
* SBCSanitize implements SCSI SANITIZE command (opcode 0x48)
|
||||
* For a virtual device, we zero out all blocks.
|
||||
*
|
||||
* Reference : SBC-4
|
||||
* 5.19 - SANITIZE
|
||||
*/
|
||||
func SBCSanitize(host int, cmd *api.SCSICommand) api.SAMStat {
|
||||
scb := cmd.SCB
|
||||
serviceAction := scb[1] & 0x1f
|
||||
|
||||
if cmd.Device.Attrs.Readonly || cmd.Device.Attrs.SWP {
|
||||
BuildSenseData(cmd, DATA_PROTECT, ASC_WRITE_PROTECT)
|
||||
return api.SAMStatCheckCondition
|
||||
}
|
||||
|
||||
switch serviceAction {
|
||||
case 0x01, 0x02: // OVERWRITE, BLOCK ERASE
|
||||
size := cmd.Device.Size
|
||||
blockSize := uint64(1 << cmd.Device.BlockShift)
|
||||
zeros := make([]byte, blockSize)
|
||||
var offset uint64
|
||||
for offset = 0; offset < size; offset += blockSize {
|
||||
remaining := size - offset
|
||||
writeLen := blockSize
|
||||
if remaining < blockSize {
|
||||
writeLen = remaining
|
||||
}
|
||||
if err := cmd.Device.Storage.Write(zeros[:writeLen], int64(offset)); err != nil {
|
||||
log.Errorf("Sanitize failed at offset %d: %v", offset, err)
|
||||
BuildSenseData(cmd, MEDIUM_ERROR, ASC_WRITE_ERROR)
|
||||
return api.SAMStatCheckCondition
|
||||
}
|
||||
}
|
||||
return api.SAMStatGood
|
||||
|
||||
case 0x03: // CRYPTO ERASE - not supported for virtual device
|
||||
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
||||
return api.SAMStatCheckCondition
|
||||
|
||||
case 0x1f: // EXIT FAILURE MODE
|
||||
return api.SAMStatGood
|
||||
|
||||
default:
|
||||
BuildSenseData(cmd, ILLEGAL_REQUEST, ASC_INVALID_FIELD_IN_CDB)
|
||||
return api.SAMStatCheckCondition
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ limitations under the License.
|
||||
// SCSI block command processing
|
||||
package scsi
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gostor/gotgt/pkg/api"
|
||||
)
|
||||
|
||||
func TestSBCModeSelect(t *testing.T) {
|
||||
}
|
||||
@@ -54,3 +58,132 @@ func TestSBCGetLbaStatus(t *testing.T) {
|
||||
|
||||
func TestSBCSyncCache(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSBCReadDefectData10(t *testing.T) {
|
||||
cmd := &api.SCSICommand{}
|
||||
cmd.Device = &api.SCSILu{BlockShift: 9}
|
||||
cmd.InSDBBuffer = &api.SCSIDataBuffer{
|
||||
Length: 256,
|
||||
Buffer: make([]byte, 256),
|
||||
}
|
||||
// READ DEFECT DATA(10) CDB: opcode=0x37, PLIST=1, GLIST=1, format=0
|
||||
cmd.SCB = []byte{byte(api.READ_DEFECT_DATA), 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}
|
||||
|
||||
result := SBCReadDefectData(0, cmd)
|
||||
if result != api.SAMStatGood {
|
||||
t.Errorf("ReadDefectData10 expected SAMStatGood, got %v", result)
|
||||
}
|
||||
if cmd.InSDBBuffer.Resid != 4 {
|
||||
t.Errorf("ReadDefectData10 expected Resid=4, got %d", cmd.InSDBBuffer.Resid)
|
||||
}
|
||||
// byte 1 should echo back PLIST|GLIST|format
|
||||
if cmd.InSDBBuffer.Buffer[1] != 0x18 {
|
||||
t.Errorf("ReadDefectData10 byte 1 expected 0x18, got 0x%02x", cmd.InSDBBuffer.Buffer[1])
|
||||
}
|
||||
// defect list length should be 0
|
||||
if cmd.InSDBBuffer.Buffer[2] != 0 || cmd.InSDBBuffer.Buffer[3] != 0 {
|
||||
t.Errorf("ReadDefectData10 defect list length should be 0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSBCReadDefectData12(t *testing.T) {
|
||||
cmd := &api.SCSICommand{}
|
||||
cmd.Device = &api.SCSILu{BlockShift: 9}
|
||||
cmd.InSDBBuffer = &api.SCSIDataBuffer{
|
||||
Length: 256,
|
||||
Buffer: make([]byte, 256),
|
||||
}
|
||||
// READ DEFECT DATA(12) CDB: opcode=0xB7, PLIST=1, GLIST=1, format=0
|
||||
cmd.SCB = []byte{byte(api.READ_DEFECT_DATA_12), 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}
|
||||
|
||||
result := SBCReadDefectData(0, cmd)
|
||||
if result != api.SAMStatGood {
|
||||
t.Errorf("ReadDefectData12 expected SAMStatGood, got %v", result)
|
||||
}
|
||||
if cmd.InSDBBuffer.Resid != 8 {
|
||||
t.Errorf("ReadDefectData12 expected Resid=8, got %d", cmd.InSDBBuffer.Resid)
|
||||
}
|
||||
// defect list length (bytes 4-7) should be 0
|
||||
for i := 4; i < 8; i++ {
|
||||
if cmd.InSDBBuffer.Buffer[i] != 0 {
|
||||
t.Errorf("ReadDefectData12 byte %d expected 0, got %d", i, cmd.InSDBBuffer.Buffer[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSBCSanitizeInvalidServiceAction(t *testing.T) {
|
||||
cmd := &api.SCSICommand{}
|
||||
cmd.Device = &api.SCSILu{
|
||||
BlockShift: 9,
|
||||
Attrs: api.SCSILuPhyAttribute{Online: true},
|
||||
}
|
||||
// SANITIZE with invalid service action 0x00
|
||||
cmd.SCB = []byte{byte(api.SANITIZE), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
result := SBCSanitize(0, cmd)
|
||||
if result != api.SAMStatCheckCondition {
|
||||
t.Errorf("Sanitize with invalid SA expected SAMStatCheckCondition, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSBCSanitizeReadonly(t *testing.T) {
|
||||
cmd := &api.SCSICommand{}
|
||||
cmd.Device = &api.SCSILu{
|
||||
BlockShift: 9,
|
||||
Attrs: api.SCSILuPhyAttribute{Online: true, Readonly: true},
|
||||
}
|
||||
// SANITIZE OVERWRITE on readonly device
|
||||
cmd.SCB = []byte{byte(api.SANITIZE), 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
result := SBCSanitize(0, cmd)
|
||||
if result != api.SAMStatCheckCondition {
|
||||
t.Errorf("Sanitize on readonly expected SAMStatCheckCondition, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSBCSanitizeExitFailureMode(t *testing.T) {
|
||||
cmd := &api.SCSICommand{}
|
||||
cmd.Device = &api.SCSILu{
|
||||
BlockShift: 9,
|
||||
Attrs: api.SCSILuPhyAttribute{Online: true},
|
||||
}
|
||||
// SANITIZE EXIT FAILURE MODE (0x1f)
|
||||
cmd.SCB = []byte{byte(api.SANITIZE), 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
result := SBCSanitize(0, cmd)
|
||||
if result != api.SAMStatGood {
|
||||
t.Errorf("Sanitize EXIT_FAILURE_MODE expected SAMStatGood, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSBCDeviceRegistersNewCommands(t *testing.T) {
|
||||
sbc := NewSBCDevice(api.TYPE_DISK)
|
||||
sbcProto := sbc.(SBCSCSIDeviceProtocol)
|
||||
|
||||
// Verify new commands are registered (not SPCIllegalOp)
|
||||
newOpcodes := []struct {
|
||||
name string
|
||||
opcode api.SCSICommandType
|
||||
}{
|
||||
{"READ_DEFECT_DATA", api.READ_DEFECT_DATA},
|
||||
{"READ_DEFECT_DATA_12", api.READ_DEFECT_DATA_12},
|
||||
{"SANITIZE", api.SANITIZE},
|
||||
}
|
||||
|
||||
for _, tc := range newOpcodes {
|
||||
op := sbcProto.SCSIDeviceOps[int(tc.opcode)]
|
||||
if op.CommandPerformFunc == nil {
|
||||
t.Errorf("Command %s (0x%02x) not registered", tc.name, tc.opcode)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify EXTENDED_COPY and RECEIVE_COPY_RESULTS are registered (as SPCIllegalOp)
|
||||
extCopyOp := sbcProto.SCSIDeviceOps[int(api.EXTENDED_COPY)]
|
||||
if extCopyOp.CommandPerformFunc == nil {
|
||||
t.Error("EXTENDED_COPY not registered")
|
||||
}
|
||||
recvCopyOp := sbcProto.SCSIDeviceOps[int(api.RECEIVE_COPY_RESULTS)]
|
||||
if recvCopyOp.CommandPerformFunc == nil {
|
||||
t.Error("RECEIVE_COPY_RESULTS not registered")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user