working on vtl features: pending drive creation, media changer creation, iscsi mapping
This commit is contained in:
@@ -113,6 +113,7 @@ func (a *App) isPublicEndpoint(path, method string) bool {
|
||||
"/storage", // Storage management page
|
||||
"/shares", // Shares page
|
||||
"/iscsi", // iSCSI page
|
||||
"/vtl", // VTL (Virtual Tape Library) page
|
||||
"/protection", // Data Protection page
|
||||
"/management", // System Management page
|
||||
"/api/docs", // API documentation
|
||||
@@ -138,17 +139,22 @@ func (a *App) isPublicEndpoint(path, method string) bool {
|
||||
// SECURITY: Only GET requests are allowed without authentication
|
||||
// POST, PUT, DELETE, PATCH require authentication
|
||||
publicReadOnlyPaths := []string{
|
||||
"/api/v1/dashboard", // Dashboard data
|
||||
"/api/v1/disks", // List disks
|
||||
"/api/v1/pools", // List pools (GET only)
|
||||
"/api/v1/pools/available", // List available pools
|
||||
"/api/v1/datasets", // List datasets (GET only)
|
||||
"/api/v1/zvols", // List ZVOLs (GET only)
|
||||
"/api/v1/shares/smb", // List SMB shares (GET only)
|
||||
"/api/v1/exports/nfs", // List NFS exports (GET only)
|
||||
"/api/v1/iscsi/targets", // List iSCSI targets (GET only)
|
||||
"/api/v1/snapshots", // List snapshots (GET only)
|
||||
"/api/v1/snapshot-policies", // List snapshot policies (GET only)
|
||||
"/api/v1/dashboard", // Dashboard data
|
||||
"/api/v1/disks", // List disks
|
||||
"/api/v1/pools", // List pools (GET only)
|
||||
"/api/v1/pools/available", // List available pools
|
||||
"/api/v1/datasets", // List datasets (GET only)
|
||||
"/api/v1/zvols", // List ZVOLs (GET only)
|
||||
"/api/v1/shares/smb", // List SMB shares (GET only)
|
||||
"/api/v1/exports/nfs", // List NFS exports (GET only)
|
||||
"/api/v1/iscsi/targets", // List iSCSI targets (GET only)
|
||||
"/api/v1/vtl/status", // VTL status (GET only)
|
||||
"/api/v1/vtl/drives", // List VTL drives (GET only)
|
||||
"/api/v1/vtl/tapes", // List VTL tapes (GET only)
|
||||
"/api/v1/vtl/changers", // List VTL media changers (GET only)
|
||||
"/api/v1/vtl/changer/status", // VTL media changer status (GET only)
|
||||
"/api/v1/snapshots", // List snapshots (GET only)
|
||||
"/api/v1/snapshot-policies", // List snapshot policies (GET only)
|
||||
}
|
||||
|
||||
for _, publicPath := range publicReadOnlyPaths {
|
||||
|
||||
@@ -24,6 +24,9 @@ type DashboardData struct {
|
||||
SMBStatus bool `json:"smb_status"`
|
||||
NFSStatus bool `json:"nfs_status"`
|
||||
ISCSIStatus bool `json:"iscsi_status"`
|
||||
VTLStatus bool `json:"vtl_status"`
|
||||
VTLDrives int `json:"vtl_drives"`
|
||||
VTLTapes int `json:"vtl_tapes"`
|
||||
} `json:"services"`
|
||||
Jobs struct {
|
||||
Total int `json:"total"`
|
||||
@@ -93,6 +96,16 @@ func (a *App) handleDashboardAPI(w http.ResponseWriter, r *http.Request) {
|
||||
data.Services.ISCSIStatus, _ = a.iscsiService.GetStatus()
|
||||
}
|
||||
|
||||
// VTL status
|
||||
if a.vtlService != nil {
|
||||
vtlStatus, err := a.vtlService.GetStatus()
|
||||
if err == nil {
|
||||
data.Services.VTLStatus = vtlStatus.ServiceRunning
|
||||
data.Services.VTLDrives = vtlStatus.DrivesOnline
|
||||
data.Services.VTLTapes = vtlStatus.TapesAvailable
|
||||
}
|
||||
}
|
||||
|
||||
// Job statistics
|
||||
allJobs := a.jobManager.List("")
|
||||
data.Jobs.Total = len(allJobs)
|
||||
|
||||
@@ -172,6 +172,24 @@ func (a *App) routes() {
|
||||
func(w http.ResponseWriter, r *http.Request) { a.handleVTLServiceControl(w, r) },
|
||||
nil, nil, nil,
|
||||
))
|
||||
a.mux.HandleFunc("/api/v1/vtl/changers", methodHandler(
|
||||
func(w http.ResponseWriter, r *http.Request) { a.handleListVTLMediaChangers(w, r) },
|
||||
nil, nil, nil, nil,
|
||||
))
|
||||
a.mux.HandleFunc("/api/v1/vtl/changer/status", methodHandler(
|
||||
func(w http.ResponseWriter, r *http.Request) { a.handleGetVTLMediaChangerStatus(w, r) },
|
||||
nil, nil, nil, nil,
|
||||
))
|
||||
a.mux.HandleFunc("/api/v1/vtl/tape/load", methodHandler(
|
||||
nil,
|
||||
func(w http.ResponseWriter, r *http.Request) { a.handleLoadTape(w, r) },
|
||||
nil, nil, nil,
|
||||
))
|
||||
a.mux.HandleFunc("/api/v1/vtl/tape/eject", methodHandler(
|
||||
nil,
|
||||
func(w http.ResponseWriter, r *http.Request) { a.handleEjectTape(w, r) },
|
||||
nil, nil, nil,
|
||||
))
|
||||
|
||||
// Job Management
|
||||
a.mux.HandleFunc("/api/v1/jobs", methodHandler(
|
||||
|
||||
@@ -51,9 +51,11 @@ func (a *App) handleListVTLTapes(w http.ResponseWriter, r *http.Request) {
|
||||
// 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)
|
||||
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 {
|
||||
@@ -66,11 +68,22 @@ func (a *App) handleCreateVTLTape(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Type == "" {
|
||||
req.Type = "LTO-5" // Default type
|
||||
if req.LibraryID <= 0 {
|
||||
writeError(w, errors.ErrValidation("library_id is required and must be greater than 0"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.vtlService.CreateTape(req.Barcode, req.Type, req.Size); err != nil {
|
||||
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
|
||||
@@ -188,3 +201,87 @@ func (a *App) handleGetVTLTape(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user