@@ -958,23 +958,61 @@ func (a *App) handleCreateISCSITarget(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleGetISCSITarget(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/iscsi/targets/")
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "target id required"})
|
||||
writeError(w, errors.ErrBadRequest("target id required"))
|
||||
return
|
||||
}
|
||||
|
||||
target, err := a.iscsiStore.Get(id)
|
||||
if err != nil {
|
||||
if err == storage.ErrISCSITargetNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
writeError(w, errors.ErrNotFound("iSCSI target"))
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
writeError(w, errors.ErrInternal("failed to get iSCSI target").WithDetails(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, target)
|
||||
}
|
||||
|
||||
func (a *App) handleGetISCSIConnectionInstructions(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/iscsi/targets/")
|
||||
if id == "" {
|
||||
writeError(w, errors.ErrBadRequest("target id required"))
|
||||
return
|
||||
}
|
||||
|
||||
target, err := a.iscsiStore.Get(id)
|
||||
if err != nil {
|
||||
if err == storage.ErrISCSITargetNotFound {
|
||||
writeError(w, errors.ErrNotFound("iSCSI target"))
|
||||
return
|
||||
}
|
||||
writeError(w, errors.ErrInternal("failed to get iSCSI target").WithDetails(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Get portal IP (with fallback)
|
||||
portalIP, err := a.iscsiService.GetPortalIP()
|
||||
if err != nil {
|
||||
log.Printf("get portal IP error: %v", err)
|
||||
portalIP = "127.0.0.1" // Fallback
|
||||
}
|
||||
|
||||
// Get portal port from query parameter or use default
|
||||
portalPort := 3260
|
||||
if portStr := r.URL.Query().Get("port"); portStr != "" {
|
||||
if port, err := strconv.Atoi(portStr); err == nil && port > 0 && port < 65536 {
|
||||
portalPort = port
|
||||
}
|
||||
}
|
||||
|
||||
// Generate connection instructions
|
||||
instructions := a.iscsiService.GetConnectionInstructions(*target, portalIP, portalPort)
|
||||
|
||||
writeJSON(w, http.StatusOK, instructions)
|
||||
}
|
||||
|
||||
func (a *App) handleUpdateISCSITarget(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/iscsi/targets/")
|
||||
if id == "" {
|
||||
|
||||
@@ -193,12 +193,27 @@ func (a *App) handleBackupOps(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *App) handleISCSITargetOps(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/iscsi/targets/")
|
||||
if id == "" {
|
||||
writeError(w, errors.ErrBadRequest("target id required"))
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(r.URL.Path, "/connection") {
|
||||
if r.Method == http.MethodGet {
|
||||
a.handleGetISCSIConnectionInstructions(w, r)
|
||||
} else {
|
||||
writeError(w, errors.NewAPIError(errors.ErrCodeBadRequest, "method not allowed", http.StatusMethodNotAllowed))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(r.URL.Path, "/luns") {
|
||||
if r.Method == http.MethodPost {
|
||||
a.handleAddLUN(w, r)
|
||||
return
|
||||
}
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
writeError(w, errors.NewAPIError(errors.ErrCodeBadRequest, "method not allowed", http.StatusMethodNotAllowed))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -207,7 +222,7 @@ func (a *App) handleISCSITargetOps(w http.ResponseWriter, r *http.Request) {
|
||||
a.handleRemoveLUN(w, r)
|
||||
return
|
||||
}
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
writeError(w, errors.NewAPIError(errors.ErrCodeBadRequest, "method not allowed", http.StatusMethodNotAllowed))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -214,3 +214,153 @@ func contains(slice []string, item string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ConnectionInstructions represents iSCSI connection instructions
|
||||
type ConnectionInstructions struct {
|
||||
IQN string `json:"iqn"`
|
||||
Portal string `json:"portal"` // IP:port
|
||||
PortalIP string `json:"portal_ip"` // IP address
|
||||
PortalPort int `json:"portal_port"` // Port (default 3260)
|
||||
LUNs []LUNInfo `json:"luns"`
|
||||
Commands Commands `json:"commands"`
|
||||
}
|
||||
|
||||
// LUNInfo represents LUN information for connection
|
||||
type LUNInfo struct {
|
||||
ID int `json:"id"`
|
||||
ZVOL string `json:"zvol"`
|
||||
Size uint64 `json:"size"`
|
||||
}
|
||||
|
||||
// Commands contains OS-specific connection commands
|
||||
type Commands struct {
|
||||
Linux []string `json:"linux"`
|
||||
Windows []string `json:"windows"`
|
||||
MacOS []string `json:"macos"`
|
||||
}
|
||||
|
||||
// GetConnectionInstructions generates connection instructions for an iSCSI target
|
||||
func (s *ISCSIService) GetConnectionInstructions(target models.ISCSITarget, portalIP string, portalPort int) *ConnectionInstructions {
|
||||
if portalPort == 0 {
|
||||
portalPort = 3260 // Default iSCSI port
|
||||
}
|
||||
|
||||
portal := fmt.Sprintf("%s:%d", portalIP, portalPort)
|
||||
|
||||
// Build LUN information
|
||||
luns := make([]LUNInfo, len(target.LUNs))
|
||||
for i, lun := range target.LUNs {
|
||||
luns[i] = LUNInfo{
|
||||
ID: lun.ID,
|
||||
ZVOL: lun.ZVOL,
|
||||
Size: lun.Size,
|
||||
}
|
||||
}
|
||||
|
||||
// Generate Linux commands
|
||||
linuxCmds := []string{
|
||||
fmt.Sprintf("# Discover target"),
|
||||
fmt.Sprintf("iscsiadm -m discovery -t sendtargets -p %s", portal),
|
||||
fmt.Sprintf(""),
|
||||
fmt.Sprintf("# Login to target"),
|
||||
fmt.Sprintf("iscsiadm -m node -T %s -p %s --login", target.IQN, portal),
|
||||
fmt.Sprintf(""),
|
||||
fmt.Sprintf("# Verify connection"),
|
||||
fmt.Sprintf("iscsiadm -m session"),
|
||||
fmt.Sprintf(""),
|
||||
fmt.Sprintf("# Logout (when done)"),
|
||||
fmt.Sprintf("iscsiadm -m node -T %s -p %s --logout", target.IQN, portal),
|
||||
}
|
||||
|
||||
// Generate Windows commands
|
||||
windowsCmds := []string{
|
||||
fmt.Sprintf("# Open PowerShell as Administrator"),
|
||||
fmt.Sprintf(""),
|
||||
fmt.Sprintf("# Add iSCSI target portal"),
|
||||
fmt.Sprintf("New-IscsiTargetPortal -TargetPortalAddress %s -TargetPortalPortNumber %d", portalIP, portalPort),
|
||||
fmt.Sprintf(""),
|
||||
fmt.Sprintf("# Connect to target"),
|
||||
fmt.Sprintf("Connect-IscsiTarget -NodeAddress %s", target.IQN),
|
||||
fmt.Sprintf(""),
|
||||
fmt.Sprintf("# Verify connection"),
|
||||
fmt.Sprintf("Get-IscsiSession"),
|
||||
fmt.Sprintf(""),
|
||||
fmt.Sprintf("# Disconnect (when done)"),
|
||||
fmt.Sprintf("Disconnect-IscsiTarget -NodeAddress %s", target.IQN),
|
||||
}
|
||||
|
||||
// Generate macOS commands
|
||||
macosCmds := []string{
|
||||
fmt.Sprintf("# macOS uses built-in iSCSI support"),
|
||||
fmt.Sprintf("# Use System Preferences > Network > iSCSI"),
|
||||
fmt.Sprintf(""),
|
||||
fmt.Sprintf("# Or use command line (if iscsiutil is available)"),
|
||||
fmt.Sprintf("iscsiutil -a -t %s -p %s", target.IQN, portal),
|
||||
fmt.Sprintf(""),
|
||||
fmt.Sprintf("# Portal: %s", portal),
|
||||
fmt.Sprintf("# Target IQN: %s", target.IQN),
|
||||
}
|
||||
|
||||
return &ConnectionInstructions{
|
||||
IQN: target.IQN,
|
||||
Portal: portal,
|
||||
PortalIP: portalIP,
|
||||
PortalPort: portalPort,
|
||||
LUNs: luns,
|
||||
Commands: Commands{
|
||||
Linux: linuxCmds,
|
||||
Windows: windowsCmds,
|
||||
MacOS: macosCmds,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetPortalIP attempts to detect the portal IP address
|
||||
func (s *ISCSIService) GetPortalIP() (string, error) {
|
||||
// Try to get IP from targetcli
|
||||
cmd := exec.Command(s.targetcliPath, "/iscsi", "ls")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Fallback: try to get system IP
|
||||
return s.getSystemIP()
|
||||
}
|
||||
|
||||
// Parse output to find portal IP
|
||||
// This is a simplified version - real implementation would parse targetcli output properly
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "ipv4") || strings.Contains(line, "ipv6") {
|
||||
// Extract IP from line
|
||||
parts := strings.Fields(line)
|
||||
for _, part := range parts {
|
||||
// Check if it looks like an IP address
|
||||
if strings.Contains(part, ".") || strings.Contains(part, ":") {
|
||||
// Remove port if present
|
||||
if idx := strings.Index(part, ":"); idx > 0 {
|
||||
return part[:idx], nil
|
||||
}
|
||||
return part, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to system IP
|
||||
return s.getSystemIP()
|
||||
}
|
||||
|
||||
// getSystemIP gets a system IP address (simplified)
|
||||
func (s *ISCSIService) getSystemIP() (string, error) {
|
||||
// Try to get IP from hostname -I (Linux)
|
||||
cmd := exec.Command("hostname", "-I")
|
||||
output, err := cmd.Output()
|
||||
if err == nil {
|
||||
ips := strings.Fields(string(output))
|
||||
if len(ips) > 0 {
|
||||
return ips[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: return localhost
|
||||
return "127.0.0.1", nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user