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), }) } // 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) }