Complete VTL implementation with SCST and mhVTL integration
- Installed and configured SCST with 7 handlers - Installed and configured mhVTL with 2 Quantum libraries and 8 LTO-8 drives - Implemented all VTL API endpoints (8/9 working) - Fixed NULL device_path handling in drives endpoint - Added comprehensive error handling and validation - Implemented async tape load/unload operations - Created SCST installation guide for Ubuntu 24.04 - Created mhVTL installation and configuration guide - Added VTL testing guide and automated test scripts - All core API tests passing (89% success rate) Infrastructure status: - PostgreSQL: Configured with proper permissions - SCST: Active with kernel module loaded - mhVTL: 2 libraries (Quantum Scalar i500, Scalar i40) - mhVTL: 8 drives (all Quantum ULTRIUM-HH8 LTO-8) - Calypso API: 8/9 VTL endpoints functional Documentation added: - src/srs-technical-spec-documents/scst-installation.md - src/srs-technical-spec-documents/mhvtl-installation.md - VTL-TESTING-GUIDE.md - scripts/test-vtl.sh Co-Authored-By: Warp <agent@warp.dev>
This commit is contained in:
169
backend/internal/storage/handler.go
Normal file
169
backend/internal/storage/handler.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"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 storage-related API requests
|
||||
type Handler struct {
|
||||
diskService *DiskService
|
||||
lvmService *LVMService
|
||||
taskEngine *tasks.Engine
|
||||
db *database.DB
|
||||
logger *logger.Logger
|
||||
}
|
||||
|
||||
// NewHandler creates a new storage handler
|
||||
func NewHandler(db *database.DB, log *logger.Logger) *Handler {
|
||||
return &Handler{
|
||||
diskService: NewDiskService(db, log),
|
||||
lvmService: NewLVMService(db, log),
|
||||
taskEngine: tasks.NewEngine(db, log),
|
||||
db: db,
|
||||
logger: log,
|
||||
}
|
||||
}
|
||||
|
||||
// ListDisks lists all physical disks
|
||||
func (h *Handler) ListDisks(c *gin.Context) {
|
||||
disks, err := h.diskService.DiscoverDisks(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to list disks", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list disks"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"disks": disks})
|
||||
}
|
||||
|
||||
// SyncDisks syncs discovered disks to database
|
||||
func (h *Handler) SyncDisks(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
// Create async task
|
||||
taskID, err := h.taskEngine.CreateTask(c.Request.Context(),
|
||||
tasks.TaskTypeRescan, userID.(string), map[string]interface{}{
|
||||
"operation": "sync_disks",
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"})
|
||||
return
|
||||
}
|
||||
|
||||
// Run sync in background
|
||||
go func() {
|
||||
ctx := c.Request.Context()
|
||||
h.taskEngine.StartTask(ctx, taskID)
|
||||
h.taskEngine.UpdateProgress(ctx, taskID, 50, "Discovering disks...")
|
||||
|
||||
if err := h.diskService.SyncDisksToDatabase(ctx); err != nil {
|
||||
h.taskEngine.FailTask(ctx, taskID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.taskEngine.UpdateProgress(ctx, taskID, 100, "Disk sync completed")
|
||||
h.taskEngine.CompleteTask(ctx, taskID, "Disks synchronized successfully")
|
||||
}()
|
||||
|
||||
c.JSON(http.StatusAccepted, gin.H{"task_id": taskID})
|
||||
}
|
||||
|
||||
// ListVolumeGroups lists all volume groups
|
||||
func (h *Handler) ListVolumeGroups(c *gin.Context) {
|
||||
vgs, err := h.lvmService.ListVolumeGroups(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to list volume groups", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list volume groups"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"volume_groups": vgs})
|
||||
}
|
||||
|
||||
// ListRepositories lists all repositories
|
||||
func (h *Handler) ListRepositories(c *gin.Context) {
|
||||
repos, err := h.lvmService.ListRepositories(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to list repositories", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list repositories"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"repositories": repos})
|
||||
}
|
||||
|
||||
// GetRepository retrieves a repository by ID
|
||||
func (h *Handler) GetRepository(c *gin.Context) {
|
||||
repoID := c.Param("id")
|
||||
|
||||
repo, err := h.lvmService.GetRepository(c.Request.Context(), repoID)
|
||||
if err != nil {
|
||||
if err.Error() == "repository not found" {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "repository not found"})
|
||||
return
|
||||
}
|
||||
h.logger.Error("Failed to get repository", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get repository"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, repo)
|
||||
}
|
||||
|
||||
// CreateRepositoryRequest represents a repository creation request
|
||||
type CreateRepositoryRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
VolumeGroup string `json:"volume_group" binding:"required"`
|
||||
SizeGB int64 `json:"size_gb" binding:"required"`
|
||||
}
|
||||
|
||||
// CreateRepository creates a new repository
|
||||
func (h *Handler) CreateRepository(c *gin.Context) {
|
||||
var req CreateRepositoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
userID, _ := c.Get("user_id")
|
||||
sizeBytes := req.SizeGB * 1024 * 1024 * 1024
|
||||
|
||||
repo, err := h.lvmService.CreateRepository(
|
||||
c.Request.Context(),
|
||||
req.Name,
|
||||
req.VolumeGroup,
|
||||
sizeBytes,
|
||||
userID.(string),
|
||||
)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to create repository", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, repo)
|
||||
}
|
||||
|
||||
// DeleteRepository deletes a repository
|
||||
func (h *Handler) DeleteRepository(c *gin.Context) {
|
||||
repoID := c.Param("id")
|
||||
|
||||
if err := h.lvmService.DeleteRepository(c.Request.Context(), repoID); err != nil {
|
||||
if err.Error() == "repository not found" {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "repository not found"})
|
||||
return
|
||||
}
|
||||
h.logger.Error("Failed to delete repository", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "repository deleted successfully"})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user