package storage import ( "errors" "fmt" "os" "os/exec" "path/filepath" "strings" ) // PoolConfig describes a configured storage pool. type PoolConfig struct { Name string `json:"name" yaml:"name"` Type string `json:"type" yaml:"type"` // dir|lvm|zfs Path string `json:"path" yaml:"path"` // for dir/zfs VG string `json:"vg" yaml:"vg"` // for lvm } // AttachRequest describes a volume attachment for a VM/CT. type AttachRequest struct { Pool string Volume string Path string // resolved path for libvirt/LXC } // ResolveVolume maps pool+vol name to a path for dir pools. func ResolveVolume(pools []PoolConfig, poolName, vol string) (string, error) { for _, p := range pools { if p.Name != poolName { continue } if p.Type == "dir" { if p.Path == "" { return "", fmt.Errorf("dir pool %s missing path", poolName) } target := filepath.Join(p.Path, vol) return target, nil } if p.Type == "lvm" { return fmt.Sprintf("/dev/%s/%s", p.VG, vol), nil } if p.Type == "zfs" { return fmt.Sprintf("%s/%s", p.Path, vol), nil } return "", fmt.Errorf("pool type %s not supported", p.Type) } return "", fmt.Errorf("pool %s not found", poolName) } // PoolExists checks if a pool is present in config and, for dir pools, if the path exists. func PoolExists(pools []PoolConfig, name string) bool { for _, p := range pools { if p.Name == name { if p.Type == "dir" && p.Path != "" { if _, err := os.Stat(p.Path); err == nil { return true } } // accept as present even if path missing to allow creation elsewhere return true } } return false } // CreateVolume creates a volume in the given pool (dir or LVM). ZFS is stubbed. func CreateVolume(pool PoolConfig, name string, sizeGB int) (string, error) { if name == "" { return "", errors.New("volume name required") } switch pool.Type { case "dir": if pool.Path == "" { return "", fmt.Errorf("dir pool %s missing path", pool.Name) } target := filepath.Join(pool.Path, name) if _, err := os.Stat(target); err == nil { return target, nil } cmd := exec.Command("qemu-img", "create", "-f", "qcow2", target, fmt.Sprintf("%dG", sizeGB)) if err := cmd.Run(); err != nil { return "", err } return target, nil case "lvm": if pool.VG == "" { return "", fmt.Errorf("lvm pool %s missing vg", pool.Name) } target := fmt.Sprintf("/dev/%s/%s", pool.VG, name) args := []string{"lvcreate", "-L", fmt.Sprintf("%dG", sizeGB), "-n", name, pool.VG} if err := exec.Command("bash", "-lc", strings.Join(args, " ")).Run(); err != nil { return "", err } return target, nil case "zfs": // Stub: in practice use `zfs create -V pool/vol` return "", fmt.Errorf("zfs volume creation not implemented") default: return "", fmt.Errorf("unsupported pool type %s", pool.Type) } }