- Fix target delete URL path mismatch (/targets/ -> /target/) - Implement target create/delete server handlers with proper validation - Add DeleteTarget method with force flag and mutex locking to SCSITargetService - Implement full LU management: create/list/delete through CLI, client, and server - Add TPGT list command to show target portal group tags - Add unit tests for target/LU router handlers and SCSI service Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
149 lines
4.5 KiB
Go
149 lines
4.5 KiB
Go
package scsi
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gostor/gotgt/pkg/api"
|
|
)
|
|
|
|
// nullBackingStore is a minimal backing store for tests.
|
|
type nullBackingStore struct {
|
|
BaseBackingStore
|
|
}
|
|
|
|
func (bs *nullBackingStore) Open(dev *api.SCSILu, path string) error { return nil }
|
|
func (bs *nullBackingStore) Close(dev *api.SCSILu) error { return nil }
|
|
func (bs *nullBackingStore) Init(dev *api.SCSILu, Opts string) error { return nil }
|
|
func (bs *nullBackingStore) Exit(dev *api.SCSILu) error { return nil }
|
|
func (bs *nullBackingStore) Size(dev *api.SCSILu) uint64 { return 0 }
|
|
func (bs *nullBackingStore) Read(offset, tl int64) ([]byte, error) { return nil, nil }
|
|
func (bs *nullBackingStore) Write(wbuf []byte, offset int64) error { return nil }
|
|
func (bs *nullBackingStore) DataSync(offset, tl int64) error { return nil }
|
|
func (bs *nullBackingStore) DataAdvise(offset, length int64, advise uint32) error { return nil }
|
|
func (bs *nullBackingStore) Unmap([]api.UnmapBlockDescriptor) error { return nil }
|
|
|
|
func init() {
|
|
if _, err := NewBackingStore("null"); err != nil {
|
|
RegisterBackingStore("null", func() (api.BackingStore, error) {
|
|
return &nullBackingStore{
|
|
BaseBackingStore: BaseBackingStore{Name: "null"},
|
|
}, nil
|
|
})
|
|
}
|
|
}
|
|
|
|
func resetTargetService() *SCSITargetService {
|
|
s := NewSCSITargetService()
|
|
s.mutex.Lock()
|
|
s.Targets = []*api.SCSITarget{}
|
|
s.mutex.Unlock()
|
|
return s
|
|
}
|
|
|
|
func TestNewSCSITarget(t *testing.T) {
|
|
s := resetTargetService()
|
|
|
|
target, err := s.NewSCSITarget(0, "iscsi", "iqn.2016-09.com.gotgt:test_target")
|
|
if err != nil {
|
|
t.Fatalf("NewSCSITarget failed: %v", err)
|
|
}
|
|
if target == nil {
|
|
t.Fatal("NewSCSITarget returned nil")
|
|
}
|
|
if target.Name != "iqn.2016-09.com.gotgt:test_target" {
|
|
t.Fatalf("expected target name iqn.2016-09.com.gotgt:test_target, got %s", target.Name)
|
|
}
|
|
if target.TID != 0 {
|
|
t.Fatalf("expected TID 0, got %d", target.TID)
|
|
}
|
|
if len(s.Targets) != 1 {
|
|
t.Fatalf("expected 1 target, got %d", len(s.Targets))
|
|
}
|
|
if len(target.TargetPortGroups) != 1 {
|
|
t.Fatalf("expected 1 target port group, got %d", len(target.TargetPortGroups))
|
|
}
|
|
}
|
|
|
|
func TestDeleteTargetSuccess(t *testing.T) {
|
|
s := resetTargetService()
|
|
|
|
_, err := s.NewSCSITarget(0, "iscsi", "iqn.2016-09.com.gotgt:delete_me")
|
|
if err != nil {
|
|
t.Fatalf("NewSCSITarget failed: %v", err)
|
|
}
|
|
|
|
err = s.DeleteTarget("iqn.2016-09.com.gotgt:delete_me", false)
|
|
if err != nil {
|
|
t.Fatalf("DeleteTarget failed: %v", err)
|
|
}
|
|
if len(s.Targets) != 0 {
|
|
t.Fatalf("expected 0 targets after deletion, got %d", len(s.Targets))
|
|
}
|
|
}
|
|
|
|
func TestDeleteTargetNotFound(t *testing.T) {
|
|
s := resetTargetService()
|
|
|
|
err := s.DeleteTarget("iqn.2016-09.com.gotgt:nonexistent", false)
|
|
if err == nil {
|
|
t.Fatal("expected error deleting nonexistent target")
|
|
}
|
|
}
|
|
|
|
func TestDeleteTargetActiveSessions(t *testing.T) {
|
|
s := resetTargetService()
|
|
|
|
target, err := s.NewSCSITarget(0, "iscsi", "iqn.2016-09.com.gotgt:busy_target")
|
|
if err != nil {
|
|
t.Fatalf("NewSCSITarget failed: %v", err)
|
|
}
|
|
|
|
// Simulate active session
|
|
target.ITNexusMutex.Lock()
|
|
target.ITNexus[uuid.New()] = &api.ITNexus{ID: uuid.New()}
|
|
target.ITNexusMutex.Unlock()
|
|
|
|
// Should fail without force
|
|
err = s.DeleteTarget("iqn.2016-09.com.gotgt:busy_target", false)
|
|
if err == nil {
|
|
t.Fatal("expected error deleting target with active sessions")
|
|
}
|
|
|
|
// Should succeed with force
|
|
err = s.DeleteTarget("iqn.2016-09.com.gotgt:busy_target", true)
|
|
if err != nil {
|
|
t.Fatalf("DeleteTarget with force failed: %v", err)
|
|
}
|
|
if len(s.Targets) != 0 {
|
|
t.Fatalf("expected 0 targets after forced deletion, got %d", len(s.Targets))
|
|
}
|
|
}
|
|
|
|
func TestDeleteTargetMultiple(t *testing.T) {
|
|
s := resetTargetService()
|
|
|
|
s.NewSCSITarget(0, "iscsi", "iqn.2016-09.com.gotgt:target_a")
|
|
s.NewSCSITarget(1, "iscsi", "iqn.2016-09.com.gotgt:target_b")
|
|
s.NewSCSITarget(2, "iscsi", "iqn.2016-09.com.gotgt:target_c")
|
|
|
|
if len(s.Targets) != 3 {
|
|
t.Fatalf("expected 3 targets, got %d", len(s.Targets))
|
|
}
|
|
|
|
// Delete middle one
|
|
err := s.DeleteTarget("iqn.2016-09.com.gotgt:target_b", false)
|
|
if err != nil {
|
|
t.Fatalf("DeleteTarget failed: %v", err)
|
|
}
|
|
if len(s.Targets) != 2 {
|
|
t.Fatalf("expected 2 targets, got %d", len(s.Targets))
|
|
}
|
|
if s.Targets[0].Name != "iqn.2016-09.com.gotgt:target_a" {
|
|
t.Fatalf("expected target_a first, got %s", s.Targets[0].Name)
|
|
}
|
|
if s.Targets[1].Name != "iqn.2016-09.com.gotgt:target_c" {
|
|
t.Fatalf("expected target_c second, got %s", s.Targets[1].Name)
|
|
}
|
|
}
|