package services import ( "fmt" "log" "os" "os/exec" "path/filepath" "strconv" "strings" "gitea.avt.data-center.id/othman.suseno/atlas/internal/models" ) // VTLService manages mhvtl (Virtual Tape Library) type VTLService struct { configPath string // Path to mhvtl config (default: /etc/mhvtl/mhvtl.conf) storagePath string // Path to tape storage (default: /opt/mhvtl) } // NewVTLService creates a new VTL service func NewVTLService() *VTLService { return &VTLService{ configPath: "/etc/mhvtl/mhvtl.conf", storagePath: "/opt/mhvtl", } } // GetStatus returns the status of mhvtl service func (s *VTLService) GetStatus() (*models.VTLStatus, error) { status := &models.VTLStatus{ ServiceRunning: false, DrivesOnline: 0, DrivesTotal: 0, TapesTotal: 0, TapesAvailable: 0, } // Check if mhvtl service is running cmd := exec.Command("systemctl", "is-active", "mhvtl") if err := cmd.Run(); err == nil { status.ServiceRunning = true } // Get drives and tapes info drives, err := s.ListDrives() if err == nil { status.DrivesTotal = len(drives) for _, drive := range drives { if drive.Status == "online" { status.DrivesOnline++ } } } tapes, err := s.ListTapes() if err == nil { status.TapesTotal = len(tapes) for _, tape := range tapes { if tape.Status == "available" { status.TapesAvailable++ } } } return status, nil } // ListDrives lists all virtual tape drives func (s *VTLService) ListDrives() ([]models.VTLDrive, error) { drives := []models.VTLDrive{} // Read from /sys/class/scsi_tape/ tapePath := "/sys/class/scsi_tape" entries, err := os.ReadDir(tapePath) if err != nil { // mhvtl might not be installed or configured return drives, nil } for _, entry := range entries { if !entry.IsDir() { continue } // Entry name format: st0, st1, etc. deviceName := entry.Name() if !strings.HasPrefix(deviceName, "st") && !strings.HasPrefix(deviceName, "nst") { continue } // Get device path devicePath := fmt.Sprintf("/dev/%s", deviceName) // Try to get vendor and product from sysfs vendorPath := fmt.Sprintf("%s/%s/device/vendor", tapePath, deviceName) productPath := fmt.Sprintf("%s/%s/device/model", tapePath, deviceName) vendor := "Unknown" product := "Unknown" if vendorBytes, err := os.ReadFile(vendorPath); err == nil { vendor = strings.TrimSpace(string(vendorBytes)) } if productBytes, err := os.ReadFile(productPath); err == nil { product = strings.TrimSpace(string(productBytes)) } // Determine drive type from product driveType := s.determineDriveType(product) // Try to get drive ID from mhvtl config or device driveID := s.getDriveIDFromDevice(deviceName) // Check if media is loaded mediaLoaded, barcode := s.checkMediaLoaded(deviceName) drive := models.VTLDrive{ ID: driveID, LibraryID: driveID / 10, // Extract library ID (tens digit) SlotID: driveID % 10, // Extract slot ID (ones digit) Vendor: vendor, Product: product, Type: driveType, Device: devicePath, Status: "online", MediaLoaded: mediaLoaded, Barcode: barcode, } drives = append(drives, drive) } return drives, nil } // ListTapes lists all virtual tapes func (s *VTLService) ListTapes() ([]models.VTLTape, error) { tapes := []models.VTLTape{} // Read tapes from storage directory tapeStoragePath := filepath.Join(s.storagePath, "data") entries, err := os.ReadDir(tapeStoragePath) if err != nil { // No tapes directory yet return tapes, nil } for _, entry := range entries { if entry.IsDir() { // Directory name is usually the barcode barcode := entry.Name() tapePath := filepath.Join(tapeStoragePath, barcode) // Get tape info tape := models.VTLTape{ Barcode: barcode, LibraryID: 10, // Default library ID SlotID: 0, // Not in library by default DriveID: -1, // Not loaded Type: "LTO-5", // Default type Size: 0, Used: 0, Status: "available", } // Try to get size from tape file tapeFile := filepath.Join(tapePath, "tape") if info, err := os.Stat(tapeFile); err == nil { tape.Size = uint64(info.Size()) tape.Used = uint64(info.Size()) // For now, assume used = size } tapes = append(tapes, tape) } } return tapes, nil } // CreateTape creates a new virtual tape func (s *VTLService) CreateTape(barcode string, tapeType string, size uint64) error { // Create tape directory tapePath := filepath.Join(s.storagePath, "data", barcode) if err := os.MkdirAll(tapePath, 0755); err != nil { return fmt.Errorf("failed to create tape directory: %v", err) } // Create tape file tapeFile := filepath.Join(tapePath, "tape") file, err := os.Create(tapeFile) if err != nil { return fmt.Errorf("failed to create tape file: %v", err) } defer file.Close() // Pre-allocate space if size is specified if size > 0 { if err := file.Truncate(int64(size)); err != nil { return fmt.Errorf("failed to pre-allocate tape space: %v", err) } } log.Printf("Created virtual tape: %s (type: %s, size: %d bytes)", barcode, tapeType, size) return nil } // DeleteTape deletes a virtual tape func (s *VTLService) DeleteTape(barcode string) error { tapePath := filepath.Join(s.storagePath, "data", barcode) if err := os.RemoveAll(tapePath); err != nil { return fmt.Errorf("failed to delete tape: %v", err) } log.Printf("Deleted virtual tape: %s", barcode) return nil } // StartService starts the mhvtl service func (s *VTLService) StartService() error { cmd := exec.Command("systemctl", "start", "mhvtl") if err := cmd.Run(); err != nil { return fmt.Errorf("failed to start mhvtl service: %v", err) } return nil } // StopService stops the mhvtl service func (s *VTLService) StopService() error { cmd := exec.Command("systemctl", "stop", "mhvtl") if err := cmd.Run(); err != nil { return fmt.Errorf("failed to stop mhvtl service: %v", err) } return nil } // RestartService restarts the mhvtl service func (s *VTLService) RestartService() error { cmd := exec.Command("systemctl", "restart", "mhvtl") if err := cmd.Run(); err != nil { return fmt.Errorf("failed to restart mhvtl service: %v", err) } return nil } // Helper functions func (s *VTLService) determineDriveType(product string) string { product = strings.ToUpper(product) if strings.Contains(product, "LTO-9") || strings.Contains(product, "TD9") { return "LTO-9" } if strings.Contains(product, "LTO-8") || strings.Contains(product, "TD8") { return "LTO-8" } if strings.Contains(product, "LTO-7") || strings.Contains(product, "TD7") { return "LTO-7" } if strings.Contains(product, "LTO-6") || strings.Contains(product, "TD6") { return "LTO-6" } if strings.Contains(product, "LTO-5") || strings.Contains(product, "TD5") { return "LTO-5" } return "Unknown" } func (s *VTLService) getDriveIDFromDevice(deviceName string) int { // Extract number from device name (e.g., "st0" -> 0, "nst1" -> 1) // For now, use a simple mapping // In real implementation, this should read from mhvtl config if strings.HasPrefix(deviceName, "nst") { numStr := strings.TrimPrefix(deviceName, "nst") if num, err := strconv.Atoi(numStr); err == nil { return num + 10 // Assume library 10 } } else if strings.HasPrefix(deviceName, "st") { numStr := strings.TrimPrefix(deviceName, "st") if num, err := strconv.Atoi(numStr); err == nil { return num + 10 // Assume library 10 } } return 0 } func (s *VTLService) checkMediaLoaded(deviceName string) (bool, string) { // Check if tape is loaded by reading from device // This is a simplified check - in real implementation, use mt or sg commands statusPath := fmt.Sprintf("/sys/class/scsi_tape/%s/device/tape_stat", deviceName) if _, err := os.Stat(statusPath); err == nil { // Tape device exists, might have media // For now, return false - real implementation should check actual status return false, "" } return false, "" }