203 lines
4.1 KiB
Go
203 lines
4.1 KiB
Go
package state
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"jagacloud/node-agent/pkg/compute/libvirt"
|
|
"jagacloud/node-agent/pkg/containers/lxc"
|
|
)
|
|
|
|
// Store persists VM/CT specs to disk and provides simple locks per ID.
|
|
type Store struct {
|
|
vmDir string
|
|
ctDir string
|
|
|
|
mu sync.Mutex
|
|
locks map[string]*sync.Mutex
|
|
}
|
|
|
|
func NewStore(vmDir, ctDir string) *Store {
|
|
return &Store{vmDir: vmDir, ctDir: ctDir, locks: make(map[string]*sync.Mutex)}
|
|
}
|
|
|
|
func (s *Store) ensureDirs() error {
|
|
for _, d := range []string{s.vmDir, s.ctDir} {
|
|
if err := os.MkdirAll(d, 0o755); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ---- Locks ----
|
|
func (s *Store) lock(id string) func() {
|
|
s.mu.Lock()
|
|
m, ok := s.locks[id]
|
|
if !ok {
|
|
m = &sync.Mutex{}
|
|
s.locks[id] = m
|
|
}
|
|
s.mu.Unlock()
|
|
m.Lock()
|
|
return m.Unlock
|
|
}
|
|
|
|
// Lock exposes a per-ID lock; caller must defer the returned unlock function.
|
|
func (s *Store) Lock(id string) func() {
|
|
return s.lock(id)
|
|
}
|
|
|
|
// ---- VM ----
|
|
func (s *Store) SaveVM(spec libvirt.VMSpec) error {
|
|
if err := s.ensureDirs(); err != nil {
|
|
return err
|
|
}
|
|
if spec.ID == "" && spec.Name != "" {
|
|
spec.ID = spec.Name
|
|
}
|
|
if spec.ID == "" {
|
|
return fmt.Errorf("vm id is required")
|
|
}
|
|
data, err := yaml.Marshal(spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := filepath.Join(s.vmDir, fmt.Sprintf("%s.yaml", spec.ID))
|
|
return os.WriteFile(path, data, 0o644)
|
|
}
|
|
|
|
func (s *Store) LoadVM(id string) (libvirt.VMSpec, error) {
|
|
var spec libvirt.VMSpec
|
|
path := filepath.Join(s.vmDir, fmt.Sprintf("%s.yaml", id))
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return spec, err
|
|
}
|
|
if err := yaml.Unmarshal(data, &spec); err != nil {
|
|
return spec, err
|
|
}
|
|
if spec.ID == "" {
|
|
spec.ID = id
|
|
}
|
|
return spec, nil
|
|
}
|
|
|
|
func (s *Store) ListVMs() ([]libvirt.VMSpec, error) {
|
|
specs := []libvirt.VMSpec{}
|
|
err := filepath.WalkDir(s.vmDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() {
|
|
return nil
|
|
}
|
|
if filepath.Ext(path) != ".yaml" {
|
|
return nil
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var spec libvirt.VMSpec
|
|
if err := yaml.Unmarshal(data, &spec); err != nil {
|
|
return err
|
|
}
|
|
if spec.ID == "" {
|
|
spec.ID = trimExt(filepath.Base(path))
|
|
}
|
|
specs = append(specs, spec)
|
|
return nil
|
|
})
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
return specs, nil
|
|
}
|
|
|
|
func (s *Store) DeleteVM(id string) error {
|
|
path := filepath.Join(s.vmDir, fmt.Sprintf("%s.yaml", id))
|
|
return os.Remove(path)
|
|
}
|
|
|
|
// ---- CT ----
|
|
func (s *Store) SaveCT(spec lxc.Spec) error {
|
|
if err := s.ensureDirs(); err != nil {
|
|
return err
|
|
}
|
|
if spec.ID == "" && spec.Name != "" {
|
|
spec.ID = spec.Name
|
|
}
|
|
if spec.ID == "" {
|
|
return fmt.Errorf("ct id is required")
|
|
}
|
|
data, err := yaml.Marshal(spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := filepath.Join(s.ctDir, fmt.Sprintf("%s.yaml", spec.ID))
|
|
return os.WriteFile(path, data, 0o644)
|
|
}
|
|
|
|
func (s *Store) LoadCT(id string) (lxc.Spec, error) {
|
|
var spec lxc.Spec
|
|
path := filepath.Join(s.ctDir, fmt.Sprintf("%s.yaml", id))
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return spec, err
|
|
}
|
|
if err := yaml.Unmarshal(data, &spec); err != nil {
|
|
return spec, err
|
|
}
|
|
if spec.ID == "" {
|
|
spec.ID = id
|
|
}
|
|
return spec, nil
|
|
}
|
|
|
|
func (s *Store) ListCTs() ([]lxc.Spec, error) {
|
|
specs := []lxc.Spec{}
|
|
err := filepath.WalkDir(s.ctDir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() {
|
|
return nil
|
|
}
|
|
if filepath.Ext(path) != ".yaml" {
|
|
return nil
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var spec lxc.Spec
|
|
if err := yaml.Unmarshal(data, &spec); err != nil {
|
|
return err
|
|
}
|
|
if spec.ID == "" {
|
|
spec.ID = trimExt(filepath.Base(path))
|
|
}
|
|
specs = append(specs, spec)
|
|
return nil
|
|
})
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
return specs, nil
|
|
}
|
|
|
|
func (s *Store) DeleteCT(id string) error {
|
|
path := filepath.Join(s.ctDir, fmt.Sprintf("%s.yaml", id))
|
|
return os.Remove(path)
|
|
}
|
|
|
|
func trimExt(name string) string {
|
|
return name[:len(name)-len(filepath.Ext(name))]
|
|
}
|