initial commit
This commit is contained in:
202
pkg/state/store.go
Normal file
202
pkg/state/store.go
Normal file
@@ -0,0 +1,202 @@
|
||||
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))]
|
||||
}
|
||||
Reference in New Issue
Block a user