296 lines
7.9 KiB
Go
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, ""
|
|
}
|