Virtualization: a disk-location picker on both create forms, locations by place, add or remove a location
Pools are shown by where their bytes land (plain files on this disk, a ZFS pool inside one file, a dataset…) rather than by driver name, which read as the NAS pool. A location can be added as a plain-files pool at any folder on the machine, and removed while nothing uses it. The driver order at Install is as it was; the labels say what each one really is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
a9b4d7969e
commit
ae72e45bf5
@@ -259,6 +259,8 @@ func (s *Server) routes() *http.ServeMux {
|
||||
m.HandleFunc("POST /api/virt/exports/run", s.auth(s.virtExportsRun))
|
||||
m.HandleFunc("POST /api/virt/exports/delete", s.auth(s.virtExportDelete))
|
||||
m.HandleFunc("GET /api/virt/sshkeys", s.auth(s.virtSSHKeys))
|
||||
m.HandleFunc("POST /api/virt/pools", s.auth(s.virtPoolCreate))
|
||||
m.HandleFunc("POST /api/virt/pools/delete", s.auth(s.virtPoolDelete))
|
||||
m.HandleFunc("GET /api/alerts", s.auth(s.alertsGet))
|
||||
m.HandleFunc("PUT /api/alerts", s.auth(s.alertsPut))
|
||||
m.HandleFunc("POST /api/alerts/test", s.auth(s.alertsTest))
|
||||
|
||||
@@ -48,6 +48,7 @@ func (s *Server) virtOverview(w http.ResponseWriter, r *http.Request) {
|
||||
imgs, _ := s.virt.Images(ctx)
|
||||
out["images"] = imgs
|
||||
out["pools"] = s.virt.Pools(ctx)
|
||||
out["default_pool"] = s.virt.DefaultPool(ctx)
|
||||
out["networks"] = s.virt.Networks(ctx)
|
||||
}
|
||||
isos := s.virt.Library(s.virt.ISOsDir())
|
||||
@@ -1094,3 +1095,70 @@ func (s *Server) virtSSHKeys(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
jsonOK(w, 200, map[string]any{"keys": keys, "user": s.user})
|
||||
}
|
||||
|
||||
// ---- locations: storage pools, added as plain-files pools at a path of his choosing ----
|
||||
|
||||
func (s *Server) virtPoolCreate(w http.ResponseWriter, r *http.Request) {
|
||||
var body struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
_ = readBody(r, &body, 8192)
|
||||
p := filepath.Clean(body.Path)
|
||||
name := strings.TrimSpace(body.Name)
|
||||
if name == "" {
|
||||
name = sensors.Seg(filepath.Base(p))
|
||||
}
|
||||
if !incus.ValidName(name) {
|
||||
jsonErr(w, 400, "name: letters, digits and dashes, starting with a letter")
|
||||
return
|
||||
}
|
||||
if !filepath.IsAbs(p) || p == "/" {
|
||||
jsonErr(w, 400, "path: an absolute path to a folder on this machine")
|
||||
return
|
||||
}
|
||||
if !s.virtReady(w, r) {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
|
||||
defer cancel()
|
||||
if st, err := os.Stat(p); err == nil && !st.IsDir() {
|
||||
jsonErr(w, 400, p+" is a file, not a folder")
|
||||
return
|
||||
} else if err != nil {
|
||||
mk := priv.Wrap("mkdir", "-p", p) // Incus wants the folder there; it fills it as root
|
||||
if out, err := exec.CommandContext(ctx, mk[0], mk[1:]...).CombinedOutput(); err != nil {
|
||||
jsonErr(w, 500, "could not make "+p+": "+strings.TrimSpace(string(out)))
|
||||
return
|
||||
}
|
||||
}
|
||||
if out, err := s.virt.Cmd(ctx, "storage", "create", name, "dir", "source="+p).CombinedOutput(); err != nil {
|
||||
jsonErr(w, 409, strings.TrimPrefix(strings.TrimSpace(string(out)), "Error: "))
|
||||
return
|
||||
}
|
||||
log.Printf("virt: location %s added at %s (from %s)", name, p, clientIP(r))
|
||||
jsonOK(w, 200, map[string]string{"name": name, "path": p})
|
||||
}
|
||||
|
||||
func (s *Server) virtPoolDelete(w http.ResponseWriter, r *http.Request) {
|
||||
var body struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
_ = readBody(r, &body, 4096)
|
||||
if !incus.ValidName(body.Name) {
|
||||
jsonErr(w, 400, "bad name")
|
||||
return
|
||||
}
|
||||
if !s.virtReady(w, r) {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
|
||||
defer cancel()
|
||||
// Incus refuses a pool anything still uses; that refusal is the answer
|
||||
if out, err := s.virt.Cmd(ctx, "storage", "delete", body.Name).CombinedOutput(); err != nil {
|
||||
jsonErr(w, 409, strings.TrimPrefix(strings.TrimSpace(string(out)), "Error: "))
|
||||
return
|
||||
}
|
||||
log.Printf("virt: location %s removed (from %s)", body.Name, clientIP(r))
|
||||
w.WriteHeader(204)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user