fix storage management and nfs
This commit is contained in:
@@ -351,9 +351,60 @@ func (a *App) handleGetDataset(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a *App) handleUpdateDataset(w http.ResponseWriter, r *http.Request) {
|
||||
name := pathParam(r, "/api/v1/datasets/")
|
||||
// TODO: Implement dataset property updates
|
||||
writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented", "name": name})
|
||||
name := pathParamFull(r, "/api/v1/datasets/")
|
||||
if name == "" {
|
||||
writeError(w, errors.ErrBadRequest("dataset name required"))
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Quota string `json:"quota"` // e.g., "10G", "1T", or "none" to remove
|
||||
Compression string `json:"compression"` // e.g., "lz4", "gzip", "off"
|
||||
Options map[string]string `json:"options"` // other ZFS properties
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, errors.ErrBadRequest("invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
// Validate dataset exists
|
||||
datasets, err := a.zfs.ListDatasets("")
|
||||
if err != nil {
|
||||
writeError(w, errors.ErrInternal("failed to validate dataset").WithDetails(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
datasetExists := false
|
||||
for _, ds := range datasets {
|
||||
if ds.Name == name {
|
||||
datasetExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !datasetExists {
|
||||
writeError(w, errors.ErrNotFound(fmt.Sprintf("dataset '%s' not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
// Update dataset properties
|
||||
if err := a.zfs.UpdateDataset(name, req.Quota, req.Compression, req.Options); err != nil {
|
||||
log.Printf("update dataset error: %v", err)
|
||||
writeError(w, errors.ErrInternal("failed to update dataset").WithDetails(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Get updated dataset info
|
||||
datasets, _ = a.zfs.ListDatasets("")
|
||||
for _, ds := range datasets {
|
||||
if ds.Name == name {
|
||||
writeJSON(w, http.StatusOK, ds)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "dataset updated", "name": name})
|
||||
}
|
||||
|
||||
func (a *App) handleDeleteDataset(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -838,14 +889,16 @@ func (a *App) handleCreateNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
writeError(w, errors.ErrValidation("invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
// Validate dataset name
|
||||
if err := validation.ValidateZFSName(req.Dataset); err != nil {
|
||||
writeError(w, errors.ErrValidation(err.Error()))
|
||||
return
|
||||
// Validate clients first
|
||||
for i, client := range req.Clients {
|
||||
if err := validation.ValidateCIDR(client); err != nil {
|
||||
writeError(w, errors.ErrValidation(fmt.Sprintf("client[%d]: %s", i, err.Error())))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Validate and sanitize path if provided
|
||||
@@ -857,51 +910,73 @@ func (a *App) handleCreateNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate clients
|
||||
for i, client := range req.Clients {
|
||||
if err := validation.ValidateCIDR(client); err != nil {
|
||||
writeError(w, errors.ErrValidation(fmt.Sprintf("client[%d]: %s", i, err.Error())))
|
||||
// Get all datasets to validate and find dataset
|
||||
datasets, err := a.zfs.ListDatasets("")
|
||||
if err != nil {
|
||||
log.Printf("list datasets error: %v", err)
|
||||
writeError(w, errors.ErrInternal("failed to validate dataset").WithDetails(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Check if req.Dataset is a filesystem path (starts with /) or a dataset name
|
||||
var datasetName string
|
||||
var datasetMountpoint string
|
||||
datasetExists := false
|
||||
|
||||
if strings.HasPrefix(req.Dataset, "/") {
|
||||
// Input is a filesystem path (mountpoint), find dataset by mountpoint
|
||||
for _, ds := range datasets {
|
||||
if ds.Mountpoint == req.Dataset {
|
||||
datasetExists = true
|
||||
datasetName = ds.Name
|
||||
datasetMountpoint = ds.Mountpoint
|
||||
break
|
||||
}
|
||||
}
|
||||
if !datasetExists {
|
||||
writeError(w, errors.ErrNotFound(fmt.Sprintf("dataset with mountpoint '%s' not found", req.Dataset)))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Input is a dataset name, validate it first
|
||||
if err := validation.ValidateZFSName(req.Dataset); err != nil {
|
||||
writeError(w, errors.ErrValidation(err.Error()))
|
||||
return
|
||||
}
|
||||
// Find dataset by name
|
||||
for _, ds := range datasets {
|
||||
if ds.Name == req.Dataset {
|
||||
datasetExists = true
|
||||
datasetName = ds.Name
|
||||
datasetMountpoint = ds.Mountpoint
|
||||
break
|
||||
}
|
||||
}
|
||||
if !datasetExists {
|
||||
writeError(w, errors.ErrNotFound(fmt.Sprintf("dataset '%s' not found", req.Dataset)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Validate dataset exists
|
||||
datasets, err := a.zfs.ListDatasets("")
|
||||
if err != nil {
|
||||
log.Printf("list datasets error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to validate dataset"})
|
||||
return
|
||||
}
|
||||
|
||||
datasetExists := false
|
||||
for _, ds := range datasets {
|
||||
if ds.Name == req.Dataset {
|
||||
datasetExists = true
|
||||
if req.Path == "" {
|
||||
req.Path = ds.Mountpoint
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !datasetExists {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "dataset not found"})
|
||||
return
|
||||
// Set the correct dataset name and path
|
||||
req.Dataset = datasetName
|
||||
if req.Path == "" {
|
||||
req.Path = datasetMountpoint
|
||||
}
|
||||
|
||||
// Default clients to "*" (all) if not specified
|
||||
if req.Clients == nil || len(req.Clients) == 0 {
|
||||
if len(req.Clients) == 0 {
|
||||
req.Clients = []string{"*"}
|
||||
}
|
||||
|
||||
export, err := a.nfsStore.Create(req.Path, req.Dataset, req.Clients, req.ReadOnly, req.RootSquash)
|
||||
if err != nil {
|
||||
if err == storage.ErrNFSExportExists {
|
||||
writeJSON(w, http.StatusConflict, map[string]string{"error": "export for this path already exists"})
|
||||
writeError(w, errors.ErrConflict(fmt.Sprintf("export for path '%s' already exists", req.Path)))
|
||||
return
|
||||
}
|
||||
log.Printf("create NFS export error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
writeError(w, errors.ErrInternal("failed to create NFS export").WithDetails(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -909,6 +984,20 @@ func (a *App) handleCreateNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
exports := a.nfsStore.List()
|
||||
if err := a.nfsService.ApplyConfiguration(exports); err != nil {
|
||||
log.Printf("apply NFS configuration error: %v", err)
|
||||
// Export was created in store but failed to apply to system
|
||||
// Try to remove from store to maintain consistency
|
||||
if delErr := a.nfsStore.Delete(export.ID); delErr != nil {
|
||||
log.Printf("warning: failed to rollback export creation after ApplyConfiguration failure: %v", delErr)
|
||||
}
|
||||
writeError(w, errors.ErrInternal("failed to apply NFS configuration").WithDetails(fmt.Sprintf("Export was created but failed to apply to NFS service: %v", err)))
|
||||
return
|
||||
}
|
||||
|
||||
// Double-check export exists
|
||||
if _, getErr := a.nfsStore.Get(export.ID); getErr != nil {
|
||||
log.Printf("warning: export %s was created but not found in store: %v", export.ID, getErr)
|
||||
writeError(w, errors.ErrInternal("failed to verify export creation").WithDetails("Export may not have been created properly"))
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, export)
|
||||
@@ -977,17 +1066,17 @@ func (a *App) handleUpdateNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *App) handleDeleteNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
id := pathParam(r, "/api/v1/exports/nfs/")
|
||||
if id == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "export id required"})
|
||||
writeError(w, errors.ErrValidation("export id required"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.nfsStore.Delete(id); err != nil {
|
||||
if err == storage.ErrNFSExportNotFound {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
writeError(w, errors.ErrNotFound(fmt.Sprintf("NFS export '%s' not found", id)))
|
||||
return
|
||||
}
|
||||
log.Printf("delete NFS export error: %v", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
writeError(w, errors.ErrInternal("failed to delete NFS export").WithDetails(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -995,6 +1084,9 @@ func (a *App) handleDeleteNFSExport(w http.ResponseWriter, r *http.Request) {
|
||||
exports := a.nfsStore.List()
|
||||
if err := a.nfsService.ApplyConfiguration(exports); err != nil {
|
||||
log.Printf("apply NFS configuration error: %v", err)
|
||||
// Export was deleted from store but failed to apply to system
|
||||
// Log warning but don't fail the request since deletion succeeded
|
||||
log.Printf("warning: NFS export '%s' was deleted from store but failed to apply configuration: %v", id, err)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{"message": "export deleted", "id": id})
|
||||
|
||||
Reference in New Issue
Block a user