Files
atlas/internal/httpapp/vtl_handlers.go
2025-12-23 07:50:08 +00:00

514 lines
15 KiB
Go

package httpapp
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"gitea.avt.data-center.id/othman.suseno/atlas/internal/errors"
)
// VTL API Handlers
// handleGetVTLStatus returns the overall VTL system status
func (a *App) handleGetVTLStatus(w http.ResponseWriter, r *http.Request) {
status, err := a.vtlService.GetStatus()
if err != nil {
log.Printf("get VTL status error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to get VTL status: %v", err)))
return
}
writeJSON(w, http.StatusOK, status)
}
// handleListVTLDrives returns all virtual tape drives
func (a *App) handleListVTLDrives(w http.ResponseWriter, r *http.Request) {
drives, err := a.vtlService.ListDrives()
if err != nil {
log.Printf("list VTL drives error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to list VTL drives: %v", err)))
return
}
writeJSON(w, http.StatusOK, drives)
}
// handleListVTLTapes returns all virtual tapes
func (a *App) handleListVTLTapes(w http.ResponseWriter, r *http.Request) {
tapes, err := a.vtlService.ListTapes()
if err != nil {
log.Printf("list VTL tapes error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to list VTL tapes: %v", err)))
return
}
writeJSON(w, http.StatusOK, tapes)
}
// handleCreateVTLTape creates a new virtual tape
func (a *App) handleCreateVTLTape(w http.ResponseWriter, r *http.Request) {
var req struct {
Barcode string `json:"barcode"`
Type string `json:"type"` // e.g., "LTO-5", "LTO-6"
Size uint64 `json:"size"` // Size in bytes (0 = default, will use generation-based size)
LibraryID int `json:"library_id"` // Library ID where tape will be placed
SlotID int `json:"slot_id"` // Slot ID in library where tape will be placed
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, errors.ErrValidation(fmt.Sprintf("invalid request body: %v", err)))
return
}
if req.Barcode == "" {
writeError(w, errors.ErrValidation("barcode is required"))
return
}
if req.LibraryID <= 0 {
writeError(w, errors.ErrValidation("library_id is required and must be greater than 0"))
return
}
if req.SlotID <= 0 {
writeError(w, errors.ErrValidation("slot_id is required and must be greater than 0"))
return
}
if req.Type == "" {
// Will be determined from barcode suffix if not provided
req.Type = ""
}
if err := a.vtlService.CreateTape(req.Barcode, req.Type, req.Size, req.LibraryID, req.SlotID); err != nil {
log.Printf("create VTL tape error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to create VTL tape: %v", err)))
return
}
writeJSON(w, http.StatusCreated, map[string]string{
"message": "Virtual tape created successfully",
"barcode": req.Barcode,
})
}
// handleDeleteVTLTape deletes a virtual tape
func (a *App) handleDeleteVTLTape(w http.ResponseWriter, r *http.Request) {
barcode := pathParam(r, "barcode")
if barcode == "" {
writeError(w, errors.ErrValidation("barcode is required"))
return
}
if err := a.vtlService.DeleteTape(barcode); err != nil {
log.Printf("delete VTL tape error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to delete VTL tape: %v", err)))
return
}
writeJSON(w, http.StatusOK, map[string]string{
"message": "Virtual tape deleted successfully",
"barcode": barcode,
})
}
// handleVTLServiceControl controls the mhvtl service (start/stop/restart)
func (a *App) handleVTLServiceControl(w http.ResponseWriter, r *http.Request) {
var req struct {
Action string `json:"action"` // "start", "stop", "restart"
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, errors.ErrValidation(fmt.Sprintf("invalid request body: %v", err)))
return
}
var err error
switch req.Action {
case "start":
err = a.vtlService.StartService()
case "stop":
err = a.vtlService.StopService()
case "restart":
err = a.vtlService.RestartService()
default:
writeError(w, errors.ErrValidation("invalid action: must be 'start', 'stop', or 'restart'"))
return
}
if err != nil {
log.Printf("VTL service control error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to %s VTL service: %v", req.Action, err)))
return
}
writeJSON(w, http.StatusOK, map[string]string{
"message": "VTL service " + req.Action + "ed successfully",
"action": req.Action,
})
}
// handleGetVTLDrive returns a specific drive by ID
func (a *App) handleGetVTLDrive(w http.ResponseWriter, r *http.Request) {
driveIDStr := pathParam(r, "id")
driveID, err := strconv.Atoi(driveIDStr)
if err != nil {
writeError(w, errors.ErrValidation(fmt.Sprintf("invalid drive ID: %v", err)))
return
}
drives, err := a.vtlService.ListDrives()
if err != nil {
log.Printf("list VTL drives error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to list VTL drives: %v", err)))
return
}
for _, drive := range drives {
if drive.ID == driveID {
writeJSON(w, http.StatusOK, drive)
return
}
}
writeError(w, errors.ErrNotFound("drive not found"))
}
// handleGetVTLTape returns a specific tape by barcode
func (a *App) handleGetVTLTape(w http.ResponseWriter, r *http.Request) {
barcode := pathParam(r, "barcode")
if barcode == "" {
writeError(w, errors.ErrValidation("barcode is required"))
return
}
tapes, err := a.vtlService.ListTapes()
if err != nil {
log.Printf("list VTL tapes error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to list VTL tapes: %v", err)))
return
}
for _, tape := range tapes {
if tape.Barcode == barcode {
writeJSON(w, http.StatusOK, tape)
return
}
}
writeError(w, errors.ErrNotFound("tape not found"))
}
// handleListVTLMediaChangers returns all media changers
func (a *App) handleListVTLMediaChangers(w http.ResponseWriter, r *http.Request) {
changers, err := a.vtlService.ListMediaChangers()
if err != nil {
log.Printf("list VTL media changers error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to list VTL media changers: %v", err)))
return
}
writeJSON(w, http.StatusOK, changers)
}
// handleGetVTLMediaChangerStatus returns media changer status (all changers)
func (a *App) handleGetVTLMediaChangerStatus(w http.ResponseWriter, r *http.Request) {
changers, err := a.vtlService.ListMediaChangers()
if err != nil {
log.Printf("get VTL media changer status error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to get VTL media changer status: %v", err)))
return
}
// Return all changers, or first one if only one is requested
if len(changers) == 0 {
writeError(w, errors.ErrNotFound("no media changer found"))
return
}
// Return all changers as array
writeJSON(w, http.StatusOK, changers)
}
// handleLoadTape loads a tape into a drive
func (a *App) handleLoadTape(w http.ResponseWriter, r *http.Request) {
var req struct {
DriveID int `json:"drive_id"`
Barcode string `json:"barcode"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, errors.ErrValidation(fmt.Sprintf("invalid request body: %v", err)))
return
}
if req.Barcode == "" {
writeError(w, errors.ErrValidation("barcode is required"))
return
}
if err := a.vtlService.LoadTape(req.DriveID, req.Barcode); err != nil {
log.Printf("load tape error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to load tape: %v", err)))
return
}
writeJSON(w, http.StatusOK, map[string]string{
"message": "Tape loaded successfully",
"barcode": req.Barcode,
"drive_id": fmt.Sprintf("%d", req.DriveID),
})
}
// handleEjectTape ejects a tape from a drive
func (a *App) handleEjectTape(w http.ResponseWriter, r *http.Request) {
var req struct {
DriveID int `json:"drive_id"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, errors.ErrValidation(fmt.Sprintf("invalid request body: %v", err)))
return
}
if err := a.vtlService.EjectTape(req.DriveID); err != nil {
log.Printf("eject tape error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to eject tape: %v", err)))
return
}
writeJSON(w, http.StatusOK, map[string]string{
"message": "Tape ejected successfully",
"drive_id": fmt.Sprintf("%d", req.DriveID),
})
}
// handleCreateMediaChanger creates a new media changer/library
func (a *App) handleCreateMediaChanger(w http.ResponseWriter, r *http.Request) {
var req struct {
LibraryID int `json:"library_id"`
Vendor string `json:"vendor"`
Product string `json:"product"`
Serial string `json:"serial"`
NumSlots int `json:"num_slots"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, errors.ErrValidation(fmt.Sprintf("invalid request body: %v", err)))
return
}
if req.LibraryID <= 0 {
writeError(w, errors.ErrValidation("library_id must be greater than 0"))
return
}
if req.NumSlots <= 0 {
req.NumSlots = 10 // Default number of slots
}
if err := a.vtlService.AddMediaChanger(req.LibraryID, req.Vendor, req.Product, req.Serial, req.NumSlots); err != nil {
log.Printf("create media changer error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to create media changer: %v", err)))
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"message": "Media changer created successfully",
"library_id": req.LibraryID,
})
}
// handleUpdateMediaChanger updates a media changer/library configuration
func (a *App) handleUpdateMediaChanger(w http.ResponseWriter, r *http.Request) {
libraryIDStr := pathParam(r, "/api/v1/vtl/changers/")
libraryID, err := strconv.Atoi(libraryIDStr)
if err != nil || libraryID <= 0 {
writeError(w, errors.ErrValidation("invalid library_id"))
return
}
var req struct {
Vendor string `json:"vendor"`
Product string `json:"product"`
Serial string `json:"serial"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, errors.ErrValidation(fmt.Sprintf("invalid request body: %v", err)))
return
}
if err := a.vtlService.UpdateMediaChanger(libraryID, req.Vendor, req.Product, req.Serial); err != nil {
log.Printf("update media changer error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to update media changer: %v", err)))
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "Media changer updated successfully",
"library_id": libraryID,
})
}
// handleDeleteMediaChanger removes a media changer/library
func (a *App) handleDeleteMediaChanger(w http.ResponseWriter, r *http.Request) {
libraryIDStr := pathParam(r, "/api/v1/vtl/changers/")
libraryID, err := strconv.Atoi(libraryIDStr)
if err != nil || libraryID <= 0 {
writeError(w, errors.ErrValidation("invalid library_id"))
return
}
if err := a.vtlService.RemoveMediaChanger(libraryID); err != nil {
log.Printf("delete media changer error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to delete media changer: %v", err)))
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "Media changer deleted successfully",
"library_id": libraryID,
})
}
// handleCreateVTLDrive creates a new drive
func (a *App) handleCreateVTLDrive(w http.ResponseWriter, r *http.Request) {
var req struct {
DriveID int `json:"drive_id"`
LibraryID int `json:"library_id"`
SlotID int `json:"slot_id"`
Vendor string `json:"vendor"`
Product string `json:"product"`
Serial string `json:"serial"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, errors.ErrValidation(fmt.Sprintf("invalid request body: %v", err)))
return
}
if req.DriveID <= 0 {
writeError(w, errors.ErrValidation("drive_id must be greater than 0"))
return
}
if req.LibraryID <= 0 {
writeError(w, errors.ErrValidation("library_id must be greater than 0"))
return
}
if req.SlotID <= 0 {
writeError(w, errors.ErrValidation("slot_id must be greater than 0"))
return
}
if err := a.vtlService.AddDrive(req.DriveID, req.LibraryID, req.SlotID, req.Vendor, req.Product, req.Serial); err != nil {
log.Printf("create VTL drive error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to create VTL drive: %v", err)))
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"message": "Drive created successfully",
"drive_id": req.DriveID,
})
}
// handleUpdateVTLDrive updates a drive configuration
func (a *App) handleUpdateVTLDrive(w http.ResponseWriter, r *http.Request) {
driveIDStr := pathParam(r, "id")
driveID, err := strconv.Atoi(driveIDStr)
if err != nil || driveID <= 0 {
writeError(w, errors.ErrValidation("invalid drive_id"))
return
}
var req struct {
LibraryID int `json:"library_id"`
SlotID int `json:"slot_id"`
Vendor string `json:"vendor"`
Product string `json:"product"`
Serial string `json:"serial"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, errors.ErrValidation(fmt.Sprintf("invalid request body: %v", err)))
return
}
if err := a.vtlService.UpdateDrive(driveID, req.LibraryID, req.SlotID, req.Vendor, req.Product, req.Serial); err != nil {
log.Printf("update VTL drive error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to update VTL drive: %v", err)))
return
}
writeJSON(w, http.StatusOK, map[string]string{
"message": "Drive updated successfully",
"drive_id": fmt.Sprintf("%d", driveID),
})
}
// handleDeleteVTLDrive removes a drive
func (a *App) handleDeleteVTLDrive(w http.ResponseWriter, r *http.Request) {
driveIDStr := pathParam(r, "id")
driveID, err := strconv.Atoi(driveIDStr)
if err != nil || driveID <= 0 {
writeError(w, errors.ErrValidation("invalid drive_id"))
return
}
if err := a.vtlService.RemoveDrive(driveID); err != nil {
log.Printf("delete VTL drive error: %v", err)
writeError(w, errors.ErrInternal(fmt.Sprintf("failed to delete VTL drive: %v", err)))
return
}
writeJSON(w, http.StatusOK, map[string]string{
"message": "Drive deleted successfully",
"drive_id": fmt.Sprintf("%d", driveID),
})
}
// handleListVTLDevicesForISCSI returns all tape devices (drives and medium changers) for iSCSI passthrough
func (a *App) handleListVTLDevicesForISCSI(w http.ResponseWriter, r *http.Request) {
devices := []map[string]interface{}{}
// Get drives
drives, err := a.vtlService.ListDrives()
if err == nil {
for _, drive := range drives {
devices = append(devices, map[string]interface{}{
"type": "drive",
"device": drive.Device,
"id": drive.ID,
"library_id": drive.LibraryID,
"vendor": drive.Vendor,
"product": drive.Product,
"description": fmt.Sprintf("Tape Drive %d (Library %d) - %s %s", drive.ID, drive.LibraryID, drive.Vendor, drive.Product),
})
}
}
// Get medium changers
changers, err := a.vtlService.ListMediaChangers()
if err == nil {
for _, changer := range changers {
devices = append(devices, map[string]interface{}{
"type": "changer",
"device": changer.Device,
"id": changer.ID,
"library_id": changer.LibraryID,
"slots": changer.Slots,
"drives": changer.Drives,
"description": fmt.Sprintf("Media Changer (Library %d) - %d slots, %d drives", changer.LibraryID, changer.Slots, changer.Drives),
})
}
}
writeJSON(w, http.StatusOK, devices)
}