Implements stateless VMs

This commit is contained in:
Mikhail Klementev 2020-01-03 22:56:27 +00:00
parent e037770c38
commit 355fb314a1
No known key found for this signature in database
GPG key ID: BE44DA8C062D87DC
2 changed files with 70 additions and 16 deletions

View file

@ -13,6 +13,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"math/rand"
"net" "net"
"os" "os"
"os/exec" "os/exec"
@ -165,7 +166,8 @@ func isRunning(l *libvirt.Libvirt, name string) bool {
return err == nil return err == nil
} }
func generateAppVM(l *libvirt.Libvirt, name, appvmPath, sharedDir string, func generateAppVM(l *libvirt.Libvirt,
nixName, vmName, appvmPath, sharedDir string,
verbose, online bool) (err error) { verbose, online bool) (err error) {
err = os.Chdir(appvmPath) err = os.Chdir(appvmPath)
@ -173,12 +175,12 @@ func generateAppVM(l *libvirt.Libvirt, name, appvmPath, sharedDir string,
return return
} }
realpath, reginfo, qcow2, err := generateVM(name, verbose) realpath, reginfo, qcow2, err := generateVM(nixName, verbose)
if err != nil { if err != nil {
return return
} }
xml := generateXML(name, online, realpath, reginfo, qcow2, sharedDir) xml := generateXML(vmName, online, realpath, reginfo, qcow2, sharedDir)
_, err = l.DomainCreateXML(xml, libvirt.DomainStartValidate) _, err = l.DomainCreateXML(xml, libvirt.DomainStartValidate)
return return
} }
@ -207,14 +209,31 @@ func isAppvmConfigurationExists(appvmPath, name string) bool {
return fileExists(appvmPath + "/nix/" + name + ".nix") return fileExists(appvmPath + "/nix/" + name + ".nix")
} }
func start(l *libvirt.Libvirt, name string, verbose, online bool, func start(l *libvirt.Libvirt, name string, verbose, online, stateless bool,
args, open string) { args, open string) {
appvmPath := configDir appvmPath := configDir
vmHomePath := os.Getenv("HOME") + "/appvm/" + name + "/"
statelessName := fmt.Sprintf("tmp_%d_%s", rand.Int(), name)
sharedDir := os.Getenv("HOME") + "/appvm/"
if stateless {
sharedDir += statelessName
} else {
sharedDir += name
}
os.MkdirAll(sharedDir, 0700)
vmName := "appvm_"
if stateless {
vmName += statelessName
} else {
vmName += name
}
if open != "" { if open != "" {
filename := vmHomePath + filepath.Base(open) filename := sharedDir + "/" + filepath.Base(open)
err := copyFile(open, filename) err := copyFile(open, filename)
if err != nil { if err != nil {
log.Println("Can't copy file") log.Println("Can't copy file")
@ -225,7 +244,7 @@ func start(l *libvirt.Libvirt, name string, verbose, online bool,
} }
if args != "" { if args != "" {
err := ioutil.WriteFile(vmHomePath+".args", []byte(args), 0700) err := ioutil.WriteFile(sharedDir+"/"+".args", []byte(args), 0700)
if err != nil { if err != nil {
log.Println("Can't write args") log.Println("Can't write args")
return return
@ -248,21 +267,19 @@ func start(l *libvirt.Libvirt, name string, verbose, online bool,
log.Fatal(err) log.Fatal(err)
} }
if !isRunning(l, name) { if !isRunning(l, vmName) {
if !verbose { if !verbose {
go stupidProgressBar() go stupidProgressBar()
} }
sharedDir := fmt.Sprintf(os.Getenv("HOME") + "/appvm/" + name) err = generateAppVM(l, name, vmName, appvmPath, sharedDir,
os.MkdirAll(sharedDir, 0700) verbose, online)
err = generateAppVM(l, name, appvmPath, sharedDir, verbose, online)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
cmd := exec.Command("virt-viewer", "-c", "qemu:///system", "appvm_"+name) cmd := exec.Command("virt-viewer", "-c", "qemu:///system", vmName)
cmd.Start() cmd.Start()
} }
@ -366,9 +383,40 @@ func sync() {
log.Println("Done") log.Println("Done")
} }
func cleanupStatelessVMs(l *libvirt.Libvirt) {
domains, err := l.Domains()
if err != nil {
log.Fatal(err)
}
dirs, err := ioutil.ReadDir(appvmHomesDir)
if err != nil {
log.Fatal(err)
}
for _, f := range dirs {
if !strings.HasPrefix(f.Name(), "tmp_") {
continue
}
alive := false
for _, d := range domains {
if d.Name == "appvm_"+f.Name() {
alive = true
}
}
if !alive {
os.RemoveAll(appvmHomesDir + f.Name())
}
}
}
var configDir = os.Getenv("HOME") + "/.config/appvm/" var configDir = os.Getenv("HOME") + "/.config/appvm/"
var appvmHomesDir = os.Getenv("HOME") + "/appvm/"
func main() { func main() {
rand.Seed(time.Now().UnixNano())
os.Mkdir(os.Getenv("HOME")+"/appvm", 0700) os.Mkdir(os.Getenv("HOME")+"/appvm", 0700)
os.MkdirAll(configDir+"/nix", 0700) os.MkdirAll(configDir+"/nix", 0700)
@ -394,6 +442,8 @@ func main() {
} }
defer l.Disconnect() defer l.Disconnect()
cleanupStatelessVMs(l)
kingpin.Command("list", "List applications") kingpin.Command("list", "List applications")
autoballonCommand := kingpin.Command("autoballoon", "Automatically adjust/reduce app vm memory") autoballonCommand := kingpin.Command("autoballoon", "Automatically adjust/reduce app vm memory")
minMemory := autoballonCommand.Flag("min-memory", "Set minimal memory (megabytes)").Default("1024").Uint64() minMemory := autoballonCommand.Flag("min-memory", "Set minimal memory (megabytes)").Default("1024").Uint64()
@ -405,6 +455,7 @@ func main() {
startArgs := startCommand.Flag("args", "Command line arguments").String() startArgs := startCommand.Flag("args", "Command line arguments").String()
startOpen := startCommand.Flag("open", "Pass file to application").String() startOpen := startCommand.Flag("open", "Pass file to application").String()
startOffline := startCommand.Flag("offline", "Disconnect").Bool() startOffline := startCommand.Flag("offline", "Disconnect").Bool()
startStateless := startCommand.Flag("stateless", "Do not use default state directory").Bool()
stopName := kingpin.Command("stop", "Stop application").Arg("name", "Application name").Required().String() stopName := kingpin.Command("stop", "Stop application").Arg("name", "Application name").Required().String()
dropName := kingpin.Command("drop", "Remove application data").Arg("name", "Application name").Required().String() dropName := kingpin.Command("drop", "Remove application data").Arg("name", "Application name").Required().String()
@ -427,7 +478,8 @@ func main() {
case "generate": case "generate":
generate(l, *generateName, *generateBin, *generateVMName) generate(l, *generateName, *generateBin, *generateVMName)
case "start": case "start":
start(l, *startName, !*startQuiet, !*startOffline, start(l, *startName,
!*startQuiet, !*startOffline, *startStateless,
*startArgs, *startOpen) *startArgs, *startOpen)
case "stop": case "stop":
stop(l, *stopName) stop(l, *stopName)

6
xml.go
View file

@ -5,7 +5,9 @@ import "fmt"
// You may think that you want to rewrite to proper golang structures. // You may think that you want to rewrite to proper golang structures.
// Believe me, you shouldn't. // Believe me, you shouldn't.
func generateXML(name string, online bool, vmNixPath, reginfo, img, sharedDir string) string { func generateXML(vmName string, online bool,
vmNixPath, reginfo, img, sharedDir string) string {
qemuParams := ` qemuParams := `
<qemu:commandline> <qemu:commandline>
<qemu:arg value='-device'/> <qemu:arg value='-device'/>
@ -24,7 +26,7 @@ func generateXML(name string, online bool, vmNixPath, reginfo, img, sharedDir st
` `
} }
return fmt.Sprintf(xmlTmpl, "appvm_"+name, vmNixPath, vmNixPath, vmNixPath, return fmt.Sprintf(xmlTmpl, vmName, vmNixPath, vmNixPath, vmNixPath,
reginfo, img, sharedDir, sharedDir, sharedDir, qemuParams) reginfo, img, sharedDir, sharedDir, sharedDir, qemuParams)
} }