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