BAMS initial project structure

This commit is contained in:
2025-12-23 18:34:39 +00:00
parent e1df870f98
commit 861e0f65c3
24 changed files with 2495 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
package tape
import (
"fmt"
"os/exec"
"strconv"
"strings"
"github.com/bams/backend/internal/config"
"github.com/bams/backend/internal/logger"
)
type Library struct {
Status string `json:"status"`
TotalSlots int `json:"total_slots"`
ActiveDrives int `json:"active_drives"`
Model string `json:"model"`
Serial string `json:"serial"`
}
type Drive struct {
ID string `json:"id"`
Status string `json:"status"`
LoadedTape string `json:"loaded_tape,omitempty"`
Barcode string `json:"barcode,omitempty"`
Position int `json:"position"`
}
type Slot struct {
Number int `json:"number"`
Barcode string `json:"barcode,omitempty"`
Status string `json:"status"` // "empty", "loaded", "import"
}
type Service struct {
config *config.Config
logger *logger.Logger
}
func NewService(cfg *config.Config, log *logger.Logger) *Service {
return &Service{
config: cfg,
logger: log,
}
}
func (s *Service) GetLibrary() (*Library, error) {
s.logger.Debug("Getting tape library info")
// Try to detect library using mtx or sg_lib
library := &Library{
Status: "unknown",
TotalSlots: 0,
ActiveDrives: 0,
}
// Check for mtx (media changer)
cmd := exec.Command("mtx", "-f", "/dev/sg0", "status")
output, err := cmd.CombinedOutput()
if err == nil {
// Parse mtx output
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "Storage Element") {
library.TotalSlots++
}
if strings.Contains(line, "Data Transfer Element") {
library.ActiveDrives++
}
}
library.Status = "online"
}
return library, nil
}
func (s *Service) RunInventory() error {
s.logger.Info("Running tape library inventory")
// Use mtx to inventory
cmd := exec.Command("mtx", "-f", "/dev/sg0", "inventory")
output, err := cmd.CombinedOutput()
if err != nil {
s.logger.Error("Failed to run inventory", "error", string(output))
return fmt.Errorf("inventory failed: %w", err)
}
return nil
}
func (s *Service) ListDrives() ([]*Drive, error) {
s.logger.Debug("Listing tape drives")
drives := []*Drive{}
// Detect drives (up to 8)
for i := 0; i < 8; i++ {
device := fmt.Sprintf("/dev/nst%d", i)
cmd := exec.Command("mt", "-f", device, "status")
output, err := cmd.CombinedOutput()
if err == nil {
drive := &Drive{
ID: fmt.Sprintf("drive-%d", i),
Status: "online",
Position: i,
}
// Check if tape is loaded
if strings.Contains(string(output), "ONLINE") {
drive.Status = "loaded"
}
drives = append(drives, drive)
}
}
return drives, nil
}
func (s *Service) LoadTape(driveID string, slot int) error {
s.logger.Info("Loading tape", "drive", driveID, "slot", slot)
// Extract drive number from ID
driveNum := 0
fmt.Sscanf(driveID, "drive-%d", &driveNum)
// Use mtx to load
cmd := exec.Command("mtx", "-f", "/dev/sg0", "load", strconv.Itoa(slot), strconv.Itoa(driveNum))
output, err := cmd.CombinedOutput()
if err != nil {
s.logger.Error("Failed to load tape", "error", string(output))
return fmt.Errorf("load failed: %w", err)
}
return nil
}
func (s *Service) UnloadTape(driveID string, slot int) error {
s.logger.Info("Unloading tape", "drive", driveID, "slot", slot)
// Extract drive number from ID
driveNum := 0
fmt.Sscanf(driveID, "drive-%d", &driveNum)
// Use mtx to unload
cmd := exec.Command("mtx", "-f", "/dev/sg0", "unload", strconv.Itoa(slot), strconv.Itoa(driveNum))
output, err := cmd.CombinedOutput()
if err != nil {
s.logger.Error("Failed to unload tape", "error", string(output))
return fmt.Errorf("unload failed: %w", err)
}
return nil
}
func (s *Service) ListSlots() ([]*Slot, error) {
s.logger.Debug("Listing tape slots")
slots := []*Slot{}
// Use mtx to get slot status
cmd := exec.Command("mtx", "-f", "/dev/sg0", "status")
output, err := cmd.CombinedOutput()
if err != nil {
return slots, fmt.Errorf("failed to get slot status: %w", err)
}
// Parse mtx output
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "Storage Element") {
// Parse slot information
slot := &Slot{
Status: "empty",
}
// Extract slot number and barcode from line
// This is simplified - real parsing would be more complex
slots = append(slots, slot)
}
}
return slots, nil
}