- 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>
212 lines
6.2 KiB
Go
212 lines
6.2 KiB
Go
package scst
|
|
|
|
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 SCST-related API requests
|
|
type Handler struct {
|
|
service *Service
|
|
taskEngine *tasks.Engine
|
|
db *database.DB
|
|
logger *logger.Logger
|
|
}
|
|
|
|
// NewHandler creates a new SCST 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,
|
|
}
|
|
}
|
|
|
|
// ListTargets lists all SCST targets
|
|
func (h *Handler) ListTargets(c *gin.Context) {
|
|
targets, err := h.service.ListTargets(c.Request.Context())
|
|
if err != nil {
|
|
h.logger.Error("Failed to list targets", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list targets"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"targets": targets})
|
|
}
|
|
|
|
// GetTarget retrieves a target by ID
|
|
func (h *Handler) GetTarget(c *gin.Context) {
|
|
targetID := c.Param("id")
|
|
|
|
target, err := h.service.GetTarget(c.Request.Context(), targetID)
|
|
if err != nil {
|
|
if err.Error() == "target not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "target not found"})
|
|
return
|
|
}
|
|
h.logger.Error("Failed to get target", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get target"})
|
|
return
|
|
}
|
|
|
|
// Get LUNs
|
|
luns, _ := h.service.GetTargetLUNs(c.Request.Context(), targetID)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"target": target,
|
|
"luns": luns,
|
|
})
|
|
}
|
|
|
|
// CreateTargetRequest represents a target creation request
|
|
type CreateTargetRequest struct {
|
|
IQN string `json:"iqn" binding:"required"`
|
|
TargetType string `json:"target_type" binding:"required"`
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description"`
|
|
SingleInitiatorOnly bool `json:"single_initiator_only"`
|
|
}
|
|
|
|
// CreateTarget creates a new SCST target
|
|
func (h *Handler) CreateTarget(c *gin.Context) {
|
|
var req CreateTargetRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
|
|
userID, _ := c.Get("user_id")
|
|
|
|
target := &Target{
|
|
IQN: req.IQN,
|
|
TargetType: req.TargetType,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
IsActive: true,
|
|
SingleInitiatorOnly: req.SingleInitiatorOnly || req.TargetType == "vtl" || req.TargetType == "physical_tape",
|
|
CreatedBy: userID.(string),
|
|
}
|
|
|
|
if err := h.service.CreateTarget(c.Request.Context(), target); err != nil {
|
|
h.logger.Error("Failed to create target", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, target)
|
|
}
|
|
|
|
// AddLUNRequest represents a LUN addition request
|
|
type AddLUNRequest struct {
|
|
DeviceName string `json:"device_name" binding:"required"`
|
|
DevicePath string `json:"device_path" binding:"required"`
|
|
LUNNumber int `json:"lun_number" binding:"required"`
|
|
HandlerType string `json:"handler_type" binding:"required"`
|
|
}
|
|
|
|
// AddLUN adds a LUN to a target
|
|
func (h *Handler) AddLUN(c *gin.Context) {
|
|
targetID := c.Param("id")
|
|
|
|
target, err := h.service.GetTarget(c.Request.Context(), targetID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "target not found"})
|
|
return
|
|
}
|
|
|
|
var req AddLUNRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
|
|
if err := h.service.AddLUN(c.Request.Context(), target.IQN, req.DeviceName, req.DevicePath, req.LUNNumber, req.HandlerType); err != nil {
|
|
h.logger.Error("Failed to add LUN", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "LUN added successfully"})
|
|
}
|
|
|
|
// AddInitiatorRequest represents an initiator addition request
|
|
type AddInitiatorRequest struct {
|
|
InitiatorIQN string `json:"initiator_iqn" binding:"required"`
|
|
}
|
|
|
|
// AddInitiator adds an initiator to a target
|
|
func (h *Handler) AddInitiator(c *gin.Context) {
|
|
targetID := c.Param("id")
|
|
|
|
target, err := h.service.GetTarget(c.Request.Context(), targetID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "target not found"})
|
|
return
|
|
}
|
|
|
|
var req AddInitiatorRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
|
|
if err := h.service.AddInitiator(c.Request.Context(), target.IQN, req.InitiatorIQN); err != nil {
|
|
h.logger.Error("Failed to add initiator", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Initiator added successfully"})
|
|
}
|
|
|
|
// ApplyConfig applies SCST configuration
|
|
func (h *Handler) ApplyConfig(c *gin.Context) {
|
|
userID, _ := c.Get("user_id")
|
|
|
|
// Create async task
|
|
taskID, err := h.taskEngine.CreateTask(c.Request.Context(),
|
|
tasks.TaskTypeApplySCST, userID.(string), map[string]interface{}{
|
|
"operation": "apply_scst_config",
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create task"})
|
|
return
|
|
}
|
|
|
|
// Run apply in background
|
|
go func() {
|
|
ctx := c.Request.Context()
|
|
h.taskEngine.StartTask(ctx, taskID)
|
|
h.taskEngine.UpdateProgress(ctx, taskID, 50, "Writing SCST configuration...")
|
|
|
|
configPath := "/etc/calypso/scst/generated.conf"
|
|
if err := h.service.WriteConfig(ctx, configPath); err != nil {
|
|
h.taskEngine.FailTask(ctx, taskID, err.Error())
|
|
return
|
|
}
|
|
|
|
h.taskEngine.UpdateProgress(ctx, taskID, 100, "SCST configuration applied")
|
|
h.taskEngine.CompleteTask(ctx, taskID, "SCST configuration applied successfully")
|
|
}()
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{"task_id": taskID})
|
|
}
|
|
|
|
// ListHandlers lists available SCST handlers
|
|
func (h *Handler) ListHandlers(c *gin.Context) {
|
|
handlers, err := h.service.DetectHandlers(c.Request.Context())
|
|
if err != nil {
|
|
h.logger.Error("Failed to list handlers", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list handlers"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"handlers": handlers})
|
|
}
|
|
|