package services import ( "fmt" "os/exec" "strings" "sync" "gitea.avt.data-center.id/othman.suseno/atlas/internal/models" ) // ISCSIService manages iSCSI target service integration type ISCSIService struct { mu sync.RWMutex targetcliPath string } // NewISCSIService creates a new iSCSI service manager func NewISCSIService() *ISCSIService { return &ISCSIService{ targetcliPath: "targetcli", } } // ApplyConfiguration applies iSCSI target configurations func (s *ISCSIService) ApplyConfiguration(targets []models.ISCSITarget) error { s.mu.Lock() defer s.mu.Unlock() // For each target, ensure it exists and is configured for _, target := range targets { if !target.Enabled { // Disable target if it exists if err := s.disableTarget(target.IQN); err != nil { // Log but continue } continue } // Create or update target if err := s.createTarget(target); err != nil { return fmt.Errorf("create target %s: %w", target.IQN, err) } // Configure ACLs if err := s.configureACLs(target); err != nil { return fmt.Errorf("configure ACLs for %s: %w", target.IQN, err) } // Configure LUNs if err := s.configureLUNs(target); err != nil { return fmt.Errorf("configure LUNs for %s: %w", target.IQN, err) } } return nil } // createTarget creates an iSCSI target func (s *ISCSIService) createTarget(target models.ISCSITarget) error { // Use targetcli to create target // Format: targetcli /iscsi create cmd := exec.Command(s.targetcliPath, "/iscsi", "create", target.IQN) if err := cmd.Run(); err != nil { // Target might already exist, which is OK // Check if it actually exists if !s.targetExists(target.IQN) { return fmt.Errorf("create target failed: %w", err) } } return nil } // configureACLs configures initiator ACLs for a target func (s *ISCSIService) configureACLs(target models.ISCSITarget) error { // Get current ACLs currentACLs, _ := s.getACLs(target.IQN) // Remove ACLs not in desired list for _, acl := range currentACLs { if !contains(target.Initiators, acl) { cmd := exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/acls", "delete", acl) cmd.Run() // Ignore errors } } // Add new ACLs for _, initiator := range target.Initiators { if !contains(currentACLs, initiator) { cmd := exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/acls", "create", initiator) if err := cmd.Run(); err != nil { return fmt.Errorf("create ACL %s: %w", initiator, err) } } } return nil } // configureLUNs configures LUNs for a target func (s *ISCSIService) configureLUNs(target models.ISCSITarget) error { // Get current LUNs currentLUNs, _ := s.getLUNs(target.IQN) // Remove LUNs not in desired list for _, lun := range currentLUNs { if !s.hasLUN(target.LUNs, lun) { cmd := exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/luns", "delete", fmt.Sprintf("lun/%d", lun)) cmd.Run() // Ignore errors } } // Add/update LUNs for _, lun := range target.LUNs { // Create LUN mapping // Format: targetcli /iscsi//tpg1/luns create /backstores/zvol/ zvolPath := "/backstores/zvol/" + lun.ZVOL // First ensure the zvol backend exists cmd := exec.Command(s.targetcliPath, "/backstores/zvol", "create", lun.ZVOL, lun.ZVOL) cmd.Run() // Ignore if already exists // Create LUN cmd = exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/luns", "create", zvolPath) if err := cmd.Run(); err != nil { // LUN might already exist if !s.hasLUNID(currentLUNs, lun.ID) { return fmt.Errorf("create LUN %d: %w", lun.ID, err) } } } return nil } // Helper functions func (s *ISCSIService) targetExists(iqn string) bool { cmd := exec.Command(s.targetcliPath, "/iscsi", "ls") output, err := cmd.Output() if err != nil { return false } return strings.Contains(string(output), iqn) } func (s *ISCSIService) getACLs(iqn string) ([]string, error) { cmd := exec.Command(s.targetcliPath, "/iscsi/"+iqn+"/tpg1/acls", "ls") _, err := cmd.Output() if err != nil { return nil, err } // Parse output to extract ACL names // This is simplified - real implementation would parse targetcli output return []string{}, nil } func (s *ISCSIService) getLUNs(iqn string) ([]int, error) { cmd := exec.Command(s.targetcliPath, "/iscsi/"+iqn+"/tpg1/luns", "ls") _, err := cmd.Output() if err != nil { return nil, err } // Parse output to extract LUN IDs // This is simplified - real implementation would parse targetcli output return []int{}, nil } func (s *ISCSIService) hasLUN(luns []models.LUN, id int) bool { for _, lun := range luns { if lun.ID == id { return true } } return false } func (s *ISCSIService) hasLUNID(luns []int, id int) bool { for _, lunID := range luns { if lunID == id { return true } } return false } func (s *ISCSIService) disableTarget(iqn string) error { cmd := exec.Command(s.targetcliPath, "/iscsi/"+iqn+"/tpg1", "set", "attribute", "enable=0") return cmd.Run() } // GetStatus returns the status of iSCSI target service func (s *ISCSIService) GetStatus() (bool, error) { // Check if targetd is running cmd := exec.Command("systemctl", "is-active", "target") if err := cmd.Run(); err == nil { return true, nil } // Fallback: check process cmd = exec.Command("pgrep", "-x", "targetd") if err := cmd.Run(); err == nil { return true, nil } return false, nil } func contains(slice []string, item string) bool { for _, s := range slice { if s == item { return true } } return false }