329 lines
9.3 KiB
Go
329 lines
9.3 KiB
Go
package tape_vtl
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/atlasos/calypso/internal/common/database"
|
|
"github.com/atlasos/calypso/internal/common/logger"
|
|
"github.com/atlasos/calypso/internal/tasks"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Handler handles virtual tape library API requests
|
|
type Handler struct {
|
|
service *Service
|
|
taskEngine *tasks.Engine
|
|
db *database.DB
|
|
logger *logger.Logger
|
|
}
|
|
|
|
// NewHandler creates a new VTL handler
|
|
func NewHandler(db *database.DB, log *logger.Logger) *Handler {
|
|
return &Handler{
|
|
service: NewService(db, log),
|
|
taskEngine: tasks.NewEngine(db, log),
|
|
db: db,
|
|
logger: log,
|
|
}
|
|
}
|
|
|
|
// ListLibraries lists all virtual tape libraries
|
|
func (h *Handler) ListLibraries(c *gin.Context) {
|
|
h.logger.Info("ListLibraries called")
|
|
libraries, err := h.service.ListLibraries(c.Request.Context())
|
|
if err != nil {
|
|
h.logger.Error("Failed to list libraries", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list libraries"})
|
|
return
|
|
}
|
|
|
|
h.logger.Info("ListLibraries result", "count", len(libraries), "is_nil", libraries == nil)
|
|
|
|
// Ensure we return an empty array instead of null
|
|
if libraries == nil {
|
|
h.logger.Warn("Libraries is nil, converting to empty array")
|
|
libraries = []VirtualTapeLibrary{}
|
|
}
|
|
|
|
h.logger.Info("Returning libraries", "count", len(libraries), "libraries", libraries)
|
|
|
|
// Ensure we always return an array, never null
|
|
if libraries == nil {
|
|
libraries = []VirtualTapeLibrary{}
|
|
}
|
|
|
|
// Force empty array if nil (double check)
|
|
if libraries == nil {
|
|
h.logger.Warn("Libraries is still nil in handler, forcing empty array")
|
|
libraries = []VirtualTapeLibrary{}
|
|
}
|
|
|
|
// Use explicit JSON marshalling to ensure empty array, not null
|
|
response := map[string]interface{}{
|
|
"libraries": libraries,
|
|
}
|
|
|
|
h.logger.Info("Response payload", "count", len(libraries), "response_type", fmt.Sprintf("%T", libraries))
|
|
|
|
// Use JSON marshalling that handles empty slices correctly
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetLibrary retrieves a library by ID
|
|
func (h *Handler) GetLibrary(c *gin.Context) {
|
|
libraryID := c.Param("id")
|
|
|
|
lib, err := h.service.GetLibrary(c.Request.Context(), libraryID)
|
|
if err != nil {
|
|
if err.Error() == "library not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "library not found"})
|
|
return
|
|
}
|
|
h.logger.Error("Failed to get library", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get library"})
|
|
return
|
|
}
|
|
|
|
// Get drives
|
|
drives, _ := h.service.GetLibraryDrives(c.Request.Context(), libraryID)
|
|
|
|
// Get tapes
|
|
tapes, _ := h.service.GetLibraryTapes(c.Request.Context(), libraryID)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"library": lib,
|
|
"drives": drives,
|
|
"tapes": tapes,
|
|
})
|
|
}
|
|
|
|
// CreateLibraryRequest represents a library creation request
|
|
type CreateLibraryRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description"`
|
|
BackingStorePath string `json:"backing_store_path" binding:"required"`
|
|
SlotCount int `json:"slot_count" binding:"required"`
|
|
DriveCount int `json:"drive_count" binding:"required"`
|
|
}
|
|
|
|
// CreateLibrary creates a new virtual tape library
|
|
func (h *Handler) CreateLibrary(c *gin.Context) {
|
|
var req CreateLibraryRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
|
|
// Validate slot and drive counts
|
|
if req.SlotCount < 1 || req.SlotCount > 1000 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "slot_count must be between 1 and 1000"})
|
|
return
|
|
}
|
|
if req.DriveCount < 1 || req.DriveCount > 8 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "drive_count must be between 1 and 8"})
|
|
return
|
|
}
|
|
|
|
userID, _ := c.Get("user_id")
|
|
|
|
lib, err := h.service.CreateLibrary(
|
|
c.Request.Context(),
|
|
req.Name,
|
|
req.Description,
|
|
req.BackingStorePath,
|
|
req.SlotCount,
|
|
req.DriveCount,
|
|
userID.(string),
|
|
)
|
|
if err != nil {
|
|
h.logger.Error("Failed to create library", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, lib)
|
|
}
|
|
|
|
// DeleteLibrary deletes a virtual tape library
|
|
func (h *Handler) DeleteLibrary(c *gin.Context) {
|
|
libraryID := c.Param("id")
|
|
|
|
if err := h.service.DeleteLibrary(c.Request.Context(), libraryID); err != nil {
|
|
if err.Error() == "library not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "library not found"})
|
|
return
|
|
}
|
|
h.logger.Error("Failed to delete library", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "library deleted successfully"})
|
|
}
|
|
|
|
// GetLibraryDrives lists drives for a library
|
|
func (h *Handler) GetLibraryDrives(c *gin.Context) {
|
|
libraryID := c.Param("id")
|
|
|
|
drives, err := h.service.GetLibraryDrives(c.Request.Context(), libraryID)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get drives", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get drives"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"drives": drives})
|
|
}
|
|
|
|
// GetLibraryTapes lists tapes for a library
|
|
func (h *Handler) GetLibraryTapes(c *gin.Context) {
|
|
libraryID := c.Param("id")
|
|
|
|
tapes, err := h.service.GetLibraryTapes(c.Request.Context(), libraryID)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get tapes", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get tapes"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"tapes": tapes})
|
|
}
|
|
|
|
// CreateTapeRequest represents a tape creation request
|
|
type CreateTapeRequest struct {
|
|
Barcode string `json:"barcode" binding:"required"`
|
|
SlotNumber int `json:"slot_number" binding:"required"`
|
|
TapeType string `json:"tape_type" binding:"required"`
|
|
SizeGB int64 `json:"size_gb" binding:"required"`
|
|
}
|
|
|
|
// CreateTape creates a new virtual tape
|
|
func (h *Handler) CreateTape(c *gin.Context) {
|
|
libraryID := c.Param("id")
|
|
|
|
var req CreateTapeRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
|
|
sizeBytes := req.SizeGB * 1024 * 1024 * 1024
|
|
|
|
tape, err := h.service.CreateTape(
|
|
c.Request.Context(),
|
|
libraryID,
|
|
req.Barcode,
|
|
req.SlotNumber,
|
|
req.TapeType,
|
|
sizeBytes,
|
|
)
|
|
if err != nil {
|
|
h.logger.Error("Failed to create tape", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, tape)
|
|
}
|
|
|
|
// LoadTapeRequest represents a load tape request
|
|
type LoadTapeRequest struct {
|
|
SlotNumber int `json:"slot_number" binding:"required"`
|
|
DriveNumber int `json:"drive_number" binding:"required"`
|
|
}
|
|
|
|
// LoadTape loads a tape from slot to drive
|
|
func (h *Handler) LoadTape(c *gin.Context) {
|
|
libraryID := c.Param("id")
|
|
|
|
var req LoadTapeRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.logger.Warn("Invalid load tape request", "error", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
userID, _ := c.Get("user_id")
|
|
|
|
// Create async task
|
|
taskID, err := h.taskEngine.CreateTask(c.Request.Context(),
|
|
tasks.TaskTypeLoadUnload, userID.(string), map[string]interface{}{
|
|
"operation": "load_tape",
|
|
"library_id": libraryID,
|
|
"slot_number": req.SlotNumber,
|
|
"drive_number": req.DriveNumber,
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"})
|
|
return
|
|
}
|
|
|
|
// Run load in background
|
|
go func() {
|
|
ctx := c.Request.Context()
|
|
h.taskEngine.StartTask(ctx, taskID)
|
|
h.taskEngine.UpdateProgress(ctx, taskID, 50, "Loading tape...")
|
|
|
|
if err := h.service.LoadTape(ctx, libraryID, req.SlotNumber, req.DriveNumber); err != nil {
|
|
h.taskEngine.FailTask(ctx, taskID, err.Error())
|
|
return
|
|
}
|
|
|
|
h.taskEngine.UpdateProgress(ctx, taskID, 100, "Tape loaded")
|
|
h.taskEngine.CompleteTask(ctx, taskID, "Tape loaded successfully")
|
|
}()
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{"task_id": taskID})
|
|
}
|
|
|
|
// UnloadTapeRequest represents an unload tape request
|
|
type UnloadTapeRequest struct {
|
|
DriveNumber int `json:"drive_number" binding:"required"`
|
|
SlotNumber int `json:"slot_number" binding:"required"`
|
|
}
|
|
|
|
// UnloadTape unloads a tape from drive to slot
|
|
func (h *Handler) UnloadTape(c *gin.Context) {
|
|
libraryID := c.Param("id")
|
|
|
|
var req UnloadTapeRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.logger.Warn("Invalid unload tape request", "error", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
userID, _ := c.Get("user_id")
|
|
|
|
// Create async task
|
|
taskID, err := h.taskEngine.CreateTask(c.Request.Context(),
|
|
tasks.TaskTypeLoadUnload, userID.(string), map[string]interface{}{
|
|
"operation": "unload_tape",
|
|
"library_id": libraryID,
|
|
"slot_number": req.SlotNumber,
|
|
"drive_number": req.DriveNumber,
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"})
|
|
return
|
|
}
|
|
|
|
// Run unload in background
|
|
go func() {
|
|
ctx := c.Request.Context()
|
|
h.taskEngine.StartTask(ctx, taskID)
|
|
h.taskEngine.UpdateProgress(ctx, taskID, 50, "Unloading tape...")
|
|
|
|
if err := h.service.UnloadTape(ctx, libraryID, req.DriveNumber, req.SlotNumber); err != nil {
|
|
h.taskEngine.FailTask(ctx, taskID, err.Error())
|
|
return
|
|
}
|
|
|
|
h.taskEngine.UpdateProgress(ctx, taskID, 100, "Tape unloaded")
|
|
h.taskEngine.CompleteTask(ctx, taskID, "Tape unloaded successfully")
|
|
}()
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{"task_id": taskID})
|
|
}
|