Skip to content

Commit

Permalink
Add support for save govm instance
Browse files Browse the repository at this point in the history
This commit adds support for creating hot (default) and cold
backups of the instance

Signed-off-by: Obed N Munoz <[email protected]>
  • Loading branch information
obedmr committed Jan 17, 2024
1 parent c018232 commit 25d3792
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 8 deletions.
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ Connects through ssh to the specified virtual machine.
| --user value | ssh login user | Yes |
| --key value | private key path (default: ~/.ssh/id_rsa) | No |

save
----

Saves a GoVM Instance
| Flag | Description | Required | Default |
|-------------|---------------------------------|----------|--------------|
| --stopVM | Stop the VM During the Snapshot | No | `false` |
| --out value | Path to backup file | No | `backup.img` |

help
----

Expand All @@ -173,13 +182,15 @@ VERSION:
0.0.0
COMMANDS:
create, c Create a new VM
list, ls List VMs
remove, delete, rm, del Remove VMs
start, up, s Start a GoVM Instance
compose, co Deploy VMs from a compose config file
ssh ssh into a running VM
help, h Shows a list of commands or help for one command
create, c Create a new VM
list, ls List VMs
remove, delete, rm, del Remove VMs
start, up, s Start a GoVM Instance
compose, co Deploy VMs from a compose config file
ssh ssh into a running VM
stop, down, d Stop a GoVM Instance
save, snapshot Save a GoVM Instance
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--workdir value Alternate working directory. Default: ~/govm
Expand Down
7 changes: 6 additions & 1 deletion engines/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ func (d *Docker) Exec(containerName string, execConfig types.ExecConfig) error {
return err
}

err = d.ContainerExecStart(d.ctx, resp.ID,
_ = d.ContainerExecStart(d.ctx, resp.ID,
types.ExecStartCheck{Detach: true, Tty: false})

ins, err := d.ContainerExecInspect(d.ctx, resp.ID)
for ins.Running || err != nil {
ins, err = d.ContainerExecInspect(d.ctx, resp.ID)
}

return err
}

Expand Down
100 changes: 100 additions & 0 deletions engines/docker/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
"github.com/govm-project/govm/internal"
"github.com/govm-project/govm/vm"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
log "github.com/sirupsen/logrus"
)

// Engine stands an entry point for the docker container services
Expand Down Expand Up @@ -167,6 +169,104 @@ func (e Engine) Stop(namespace, id string) error {
return e.docker.Stop(container.ID, "")
}

// Save a Docker container-based VM instance
func (e Engine) Save(namespace, id, outputFile string, stopVM bool) error {
fullName := internal.GenerateContainerName(namespace, id)
containerObj, err := e.docker.Inspect(fullName)
if err != nil {
return err
}

// Save base image fist into data dir
execCmd := []string{}
execConfig := types.ExecConfig{
AttachStdout: true,
Detach: false,
Cmd: execCmd,
}
execConfig.Cmd = strings.Split("cp /image/image /data/base_image", " ")
err = e.docker.Exec(fullName, execConfig)
if err != nil {
log.Printf("Couldn't save base image from [%v]", fullName)
}

// Stop VM
if stopVM {
err = e.Stop(namespace, id)
if err != nil {
log.Printf("Couldn't stop the container [%v]", containerObj.ID)
return err
}
}

// Save VM
/// Create a Backup Container
baseContainerName := strings.Replace(containerObj.Name, "/", "", -1)
backupContainerName := fmt.Sprintf("%v-backup", baseContainerName)
_, err = e.docker.Inspect(backupContainerName)
if err != nil {
containerConfig := &container.Config{
Image: "govm/qemu",
Hostname: "backuptest",
Cmd: []string{"sh", "-c", "while true ; do sleep 1; done"},
Labels: map[string]string{},
}

dataDir := containerObj.Config.Labels["dataDir"]
currentDir, _ := os.Getwd()
mountBinds := []string{
fmt.Sprintf(vm.DataMount, dataDir),
fmt.Sprintf(vm.DataMount, currentDir) + "-out",
}
hostConfig := &container.HostConfig{
Privileged: true,
PublishAllPorts: true,
Binds: mountBinds,
}
networkConfig := &network.NetworkingConfig{}
backupContainerID, err := e.docker.Create(containerConfig, hostConfig, networkConfig, backupContainerName)
if err != nil {
log.Printf("Backup Container Error: %v", err)
}
err = e.Start(namespace, backupContainerID)
if err != nil {
log.Printf("Backup Container Starting failed: %v", err)
}
}
// Exec qemu backup commands
cmds := []string{
"rm /tmp/*",
"cp /data/base_image /data-out/",
"cp /data/cow_image.qcow2 /data-out/head.qcow2",
"qemu-img rebase -f qcow2 -F qcow2 -p -u -b /data-out/base_image /data-out/head.qcow2",
"qemu-img commit -p /data-out/head.qcow2",
fmt.Sprintf("mv /data-out/base_image /data-out/%v", outputFile),
"rm /data-out/head.qcow2",
}

for _, cmd := range cmds {
log.Println(cmd)
execConfig.Cmd = strings.Split(cmd, " ")
err = e.docker.Exec(backupContainerName, execConfig)
if err != nil {
log.Printf("Couldn't execute backup commands on [%v]", backupContainerName)
}
}

// Start VM
if stopVM {
err = e.Start(namespace, id)
if err != nil {
log.Printf("Couldn't start the container [%v]", containerObj.Name)
return err
}
}

// Remove Backup Container
govmID := strings.SplitN(backupContainerName, ".", 3)[2]
return e.Delete(namespace, govmID)
}

// List lists all the Docker container-based VM instances
// nolint: typecheck
func (e Engine) List(namespace string, all bool) ([]vm.Instance, error) {
Expand Down
1 change: 1 addition & 0 deletions engines/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ type VMEngine interface {
DeleteVM(namespace, id string) error
SSHVM(namespace, id, user, key string, term *termutil.Terminal) error
ListVM(namespace string, all bool) ([]vm.Instance, error)
SaveVM(namespace, id, outputFile string, stopVM bool) error
}
1 change: 1 addition & 0 deletions pkg/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func New() (*cli.App, error) {
&composeCommand,
&sshCommand,
&stopCommand,
&saveCommand,
},
}, nil
}
54 changes: 54 additions & 0 deletions pkg/cli/save.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cli

import (
"errors"
"fmt"
"os"

"github.com/govm-project/govm/engines/docker"
log "github.com/sirupsen/logrus"
cli "github.com/urfave/cli/v2"
)

// nolint: gochecknoglobals
var saveCommand = cli.Command{
Name: "save",
Aliases: []string{"snapshot"},
Usage: "Saves a GoVM Instance",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "out",
Value: "backup.img",
Usage: "Path to backup file",
},
&cli.BoolFlag{
Name: "stopvm",
Value: false,
Usage: "Stop the VM during snapshot",
},
},
Action: func(ctx *cli.Context) error {
if ctx.NArg() <= 0 {
err := errors.New("missing GoVM Instance name")
fmt.Println(err)
fmt.Printf("USAGE:\n govm stop [command options] [name]\n")
os.Exit(1)
}

namespace := ctx.String("namespace")
name := ctx.Args().First()
backupFile := ctx.String("out")
stopVM := ctx.Bool("stopvm")

engine := docker.Engine{}
engine.Init()
err := engine.Save(namespace, name, backupFile, stopVM)
if err != nil {
log.Fatalf("Error when saving the GoVM Instance %v: %v", name, err)
}

log.Printf("GoVM Instance %v has been successfully stopped", name)

return nil
},
}

0 comments on commit 25d3792

Please sign in to comment.