feat: implement cmd management for targets, LUNs, and TPGTs (fixes #36)

- Fix target delete URL path mismatch (/targets/ -> /target/)
- Implement target create/delete server handlers with proper validation
- Add DeleteTarget method with force flag and mutex locking to SCSITargetService
- Implement full LU management: create/list/delete through CLI, client, and server
- Add TPGT list command to show target portal group tags
- Add unit tests for target/LU router handlers and SCSI service

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lei Xue
2026-03-14 20:30:47 +08:00
parent bbd373ba0e
commit 93e1476a0f
16 changed files with 760 additions and 36 deletions

View File

@@ -16,27 +16,30 @@ limitations under the License.
package target
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gostor/gotgt/pkg/api"
"github.com/gostor/gotgt/pkg/apiserver/httputils"
"github.com/gostor/gotgt/pkg/apiserver/router"
"github.com/gostor/gotgt/pkg/scsi"
"golang.org/x/net/context"
)
// containerRouter is a router to talk with the container controller
// targetRouter is a router to talk with the target controller
type targetRouter struct {
routes []router.Route
}
// NewRouter initializes a new container router
// NewRouter initializes a new target router
func NewRouter() router.Router {
r := &targetRouter{}
r.initRoutes()
return r
}
// Routes returns the available routers to the container controller
// Routes returns the available routers to the target controller
func (r *targetRouter) Routes() []router.Route {
return r.routes
}
@@ -46,10 +49,10 @@ func (r *targetRouter) initRoutes() {
r.routes = []router.Route{
// GET
router.NewGetRoute("/target/list", r.getTargetList),
router.NewGetRoute("/target/tpgt/list", r.getTargetTPGTList),
// POST
router.NewPostRoute("/target/create", r.postTargetCreate),
router.NewPostRoute("/target/up", r.postTargetUp),
// PUT
// DELETE
router.NewDeleteRoute("/target/{name:.*}", r.deleteTarget),
}
@@ -65,7 +68,20 @@ func (r *targetRouter) getTargetList(ctx context.Context, w http.ResponseWriter,
}
func (r *targetRouter) postTargetCreate(ctx context.Context, w http.ResponseWriter, req *http.Request, vars map[string]string) error {
return nil
var opts api.TargetCreateRequest
if err := json.NewDecoder(req.Body).Decode(&opts); err != nil {
return fmt.Errorf("bad parameter: %v", err)
}
if opts.Name == "" {
return fmt.Errorf("bad parameter: target name is required")
}
service := scsi.NewSCSITargetService()
target, err := service.NewSCSITarget(len(service.Targets), "iscsi", opts.Name)
if err != nil {
return err
}
return httputils.WriteJSON(w, http.StatusCreated, target)
}
func (r *targetRouter) postTargetUp(ctx context.Context, w http.ResponseWriter, req *http.Request, vars map[string]string) error {
@@ -73,5 +89,51 @@ func (r *targetRouter) postTargetUp(ctx context.Context, w http.ResponseWriter,
}
func (r *targetRouter) deleteTarget(ctx context.Context, w http.ResponseWriter, req *http.Request, vars map[string]string) error {
name := vars["name"]
if name == "" {
return fmt.Errorf("bad parameter: target name is required")
}
if err := httputils.ParseForm(req); err != nil {
return err
}
force := httputils.BoolValue(req, "force")
service := scsi.NewSCSITargetService()
if err := service.DeleteTarget(name, force); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
return nil
}
func (r *targetRouter) getTargetTPGTList(ctx context.Context, w http.ResponseWriter, req *http.Request, vars map[string]string) error {
if err := httputils.ParseForm(req); err != nil {
return err
}
targetName := req.FormValue("target")
if targetName == "" {
return fmt.Errorf("bad parameter: target name is required")
}
service := scsi.NewSCSITargetService()
tgts, err := service.GetTargetList()
if err != nil {
return err
}
for _, tgt := range tgts {
if tgt.Name == targetName {
var result []api.TpgtInfo
for _, tpg := range tgt.TargetPortGroups {
info := api.TpgtInfo{TPGT: tpg.GroupID}
for _, port := range tpg.TargetPortGroup {
info.Portals = append(info.Portals, port.TargetPortName)
}
result = append(result, info)
}
return httputils.WriteJSON(w, http.StatusOK, result)
}
}
return fmt.Errorf("target %q not found", targetName)
}