package iscsi import ( "context" "fmt" "time" "github.com/example/storage-appliance/internal/infra/osexec" ) // Adapter wraps targetcli invocations for LIO (targetcli) management. type Adapter struct { Runner osexec.Runner } func NewAdapter(runner osexec.Runner) *Adapter { return &Adapter{Runner: runner} } // CreateTarget creates an IQN target via targetcli func (a *Adapter) CreateTarget(ctx context.Context, iqn string) error { // Use a short timeout for cli interactions ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() _, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "targetcli", "/iscsi", "create", iqn) if err != nil { return fmt.Errorf("targetcli create target failed: %v %s", err, stderr) } if code != 0 { return fmt.Errorf("targetcli create returned: %s", stderr) } return nil } // CreateBackstore creates a block backstore for a zvol device. func (a *Adapter) CreateBackstore(ctx context.Context, name, devpath string) error { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() // targetcli syntax: /backstores/block create _, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "targetcli", "/backstores/block", "create", name, devpath) if err != nil { return fmt.Errorf("targetcli create backstore failed: %v %s", err, stderr) } if code != 0 { return fmt.Errorf("targetcli backstore returned: %s", stderr) } return nil } // CreateLUN maps backstore into target's TPG1 LUNs func (a *Adapter) CreateLUN(ctx context.Context, iqn, backstoreName string, lunID int) error { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() bsPath := fmt.Sprintf("/backstores/block/%s", backstoreName) tpgPath := fmt.Sprintf("/iscsi/%s/tpg1/luns", iqn) _, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "targetcli", tpgPath, "create", bsPath) if err != nil { return fmt.Errorf("targetcli create lun failed: %v %s", err, stderr) } if code != 0 { return fmt.Errorf("targetcli create lun returned: %s", stderr) } return nil } // DeleteLUN unmaps a LUN from a target; if the LUN is mapped fail unless forced. func (a *Adapter) DeleteLUN(ctx context.Context, iqn string, lunID int) error { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() tpgPath := fmt.Sprintf("/iscsi/%s/tpg1/luns", iqn) // delete by numeric id _, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "targetcli", tpgPath, "delete", fmt.Sprintf("%d", lunID)) if err != nil { return fmt.Errorf("targetcli delete lun failed: %v %s", err, stderr) } if code != 0 { return fmt.Errorf("targetcli delete lun returned: %s", stderr) } return nil } func (a *Adapter) AddPortal(ctx context.Context, iqn, address string, port int) error { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() tpgPath := fmt.Sprintf("/iscsi/%s/tpg1/portals", iqn) addr := fmt.Sprintf("%s:%d", address, port) _, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "targetcli", tpgPath, "create", addr) if err != nil { return fmt.Errorf("targetcli add portal failed: %v %s", err, stderr) } if code != 0 { return fmt.Errorf("targetcli add portal returned: %s", stderr) } return nil } func (a *Adapter) AddACL(ctx context.Context, iqn, initiator string) error { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() aclPath := fmt.Sprintf("/iscsi/%s/tpg1/acls", iqn) _, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "targetcli", aclPath, "create", initiator) if err != nil { return fmt.Errorf("targetcli add acl failed: %v %s", err, stderr) } if code != 0 { return fmt.Errorf("targetcli add acl returned: %s", stderr) } return nil } // Save writes the configuration to storage (saving targetcli config) func (a *Adapter) Save(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() _, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "targetcli", "saveconfig") if err != nil { return fmt.Errorf("targetcli save failed: %v %s", err, stderr) } if code != 0 { return fmt.Errorf("targetcli save returned: %s", stderr) } return nil }