This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/auth"
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/auth"
|
||||||
"gitea.avt.data-center.id/othman.suseno/atlas/internal/errors"
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/errors"
|
||||||
@@ -78,14 +79,21 @@ func (a *App) handleCreatePool(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
err := a.zfs.CreatePool(req.Name, req.VDEVs, req.Options)
|
err := a.zfs.CreatePool(req.Name, req.VDEVs, req.Options)
|
||||||
|
|
||||||
// Always check if pool exists, even if creation reported an error
|
// CRITICAL: Always check if pool exists, regardless of reported error
|
||||||
// Sometimes pool is created despite errors (e.g., mountpoint issues)
|
// ZFS often reports mountpoint errors but pool is still created
|
||||||
|
// Wait a brief moment for pool to be fully registered
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
pool, getErr := a.zfs.GetPool(req.Name)
|
pool, getErr := a.zfs.GetPool(req.Name)
|
||||||
if getErr == nil {
|
if getErr == nil {
|
||||||
// Pool exists - return success even if creation reported an error
|
// Pool exists - this is success!
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("create pool reported error but pool exists: %v", err)
|
log.Printf("info: pool %s created successfully despite reported error: %v", req.Name, err)
|
||||||
}
|
}
|
||||||
|
// Set cache-control headers
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "0")
|
||||||
writeJSON(w, http.StatusCreated, pool)
|
writeJSON(w, http.StatusCreated, pool)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,13 @@ func (s *Service) CreatePool(name string, vdevs []string, options map[string]str
|
|||||||
options["canmount"] = "noauto"
|
options["canmount"] = "noauto"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add options
|
// IMPORTANT: Don't set mountpoint during pool creation
|
||||||
|
// ZFS tries to mount immediately during creation, which can fail
|
||||||
|
// We'll set mountpoint after pool is created
|
||||||
|
mountpointOption := options["mountpoint"]
|
||||||
|
delete(options, "mountpoint") // Remove from options temporarily
|
||||||
|
|
||||||
|
// Add remaining options
|
||||||
for k, v := range options {
|
for k, v := range options {
|
||||||
args = append(args, "-o", fmt.Sprintf("%s=%s", k, v))
|
args = append(args, "-o", fmt.Sprintf("%s=%s", k, v))
|
||||||
}
|
}
|
||||||
@@ -220,33 +226,41 @@ func (s *Service) CreatePool(name string, vdevs []string, options map[string]str
|
|||||||
args = append(args, name)
|
args = append(args, name)
|
||||||
args = append(args, vdevs...)
|
args = append(args, vdevs...)
|
||||||
|
|
||||||
// Create the pool
|
// Create the pool (without mountpoint to avoid mount errors)
|
||||||
_, err := s.execCommand(s.zpoolPath, args...)
|
_, err := s.execCommand(s.zpoolPath, args...)
|
||||||
|
|
||||||
// Even if creation reports an error, check if pool actually exists
|
// CRITICAL: Always check if pool exists, even if creation reported an error
|
||||||
// Sometimes ZFS reports errors but pool is still created
|
// ZFS often reports mountpoint errors but pool is still created successfully
|
||||||
if err != nil {
|
poolExists := false
|
||||||
// Check if pool exists despite the error
|
|
||||||
if existingPools, listErr := s.ListPools(); listErr == nil {
|
if existingPools, listErr := s.ListPools(); listErr == nil {
|
||||||
for _, pool := range existingPools {
|
for _, pool := range existingPools {
|
||||||
if pool.Name == name {
|
if pool.Name == name {
|
||||||
// Pool exists! Log the original error but don't fail
|
poolExists = true
|
||||||
log.Printf("warning: pool creation reported error but pool %s exists: %v", name, err)
|
|
||||||
err = nil // Clear error since pool was created
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If pool doesn't exist and we still have an error, return it
|
if poolExists {
|
||||||
|
// Pool exists! This is success, regardless of any reported errors
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("info: pool %s created successfully despite reported error: %v", name, err)
|
||||||
|
}
|
||||||
|
// Clear error since pool was created
|
||||||
|
err = nil
|
||||||
|
} else if err != nil {
|
||||||
|
// Pool doesn't exist and we have an error - return it
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pool created successfully - now set mountpoint and mount if needed
|
||||||
|
if mountpoint != "none" && mountpointOption != "" && poolExists {
|
||||||
|
// Ensure mountpoint directory exists
|
||||||
|
if err := s.createMountpointWithSudo(mountpoint); err != nil {
|
||||||
|
log.Printf("warning: failed to create mountpoint %s: %v", mountpoint, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pool created successfully - now try to set mountpoint and mount if needed
|
// Set mountpoint property on the root filesystem of the pool
|
||||||
if mountpoint != "none" {
|
|
||||||
// Set mountpoint property (in case it wasn't set during creation)
|
|
||||||
setMountpointArgs := []string{"set", fmt.Sprintf("mountpoint=%s", mountpoint), name}
|
setMountpointArgs := []string{"set", fmt.Sprintf("mountpoint=%s", mountpoint), name}
|
||||||
if _, setErr := s.execCommand(s.zfsPath, setMountpointArgs...); setErr != nil {
|
if _, setErr := s.execCommand(s.zfsPath, setMountpointArgs...); setErr != nil {
|
||||||
log.Printf("warning: failed to set mountpoint property: %v (pool created but not mounted)", setErr)
|
log.Printf("warning: failed to set mountpoint property: %v (pool created but not mounted)", setErr)
|
||||||
|
|||||||
@@ -518,21 +518,19 @@ async function createPool(e) {
|
|||||||
|
|
||||||
const data = await res.json().catch(() => null);
|
const data = await res.json().catch(() => null);
|
||||||
|
|
||||||
if (res.ok) {
|
// Always refresh pool list after creation attempt, regardless of response
|
||||||
closeModal('create-pool-modal');
|
// Wait a moment for pool creation to complete
|
||||||
e.target.reset();
|
|
||||||
// Wait a moment for pool to be fully created, then refresh
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
await loadPools();
|
|
||||||
alert('Pool created successfully');
|
|
||||||
} else {
|
|
||||||
// Even if error, check if pool was actually created
|
|
||||||
// Sometimes creation reports error but pool exists
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
await loadPools();
|
await loadPools();
|
||||||
|
|
||||||
const err = await res.json().catch(() => ({ error: 'Failed to create pool' }));
|
if (res.ok) {
|
||||||
alert(`Error: ${err.error || 'Failed to create pool'}\n\nNote: Please check if the pool was created despite the error.`);
|
closeModal('create-pool-modal');
|
||||||
|
e.target.reset();
|
||||||
|
alert('Pool created successfully');
|
||||||
|
} else {
|
||||||
|
// Check if pool appears in the list (might have been created despite error)
|
||||||
|
const err = data?.error || 'Failed to create pool';
|
||||||
|
alert(`Error: ${err}\n\nNote: The pool list has been refreshed. Please check if the pool was created.`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// On network error, still try to refresh to see if pool was created
|
// On network error, still try to refresh to see if pool was created
|
||||||
|
|||||||
Reference in New Issue
Block a user