Files
atlas/internal/services/vtl.go
2025-12-21 16:25:17 +00:00

296 lines
7.9 KiB
Go

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, ""
}