helpers/virtualbox/control.go (239 lines of code) (raw):

package virtualbox import ( "bytes" "errors" "fmt" log "github.com/Sirupsen/logrus" "net" "os" "os/exec" "regexp" "strings" "time" ) type StatusType string const ( NotFound StatusType = "notfound" PoweredOff StatusType = "poweroff" Saved StatusType = "saved" Teleported StatusType = "teleported" Aborted StatusType = "aborted" Running StatusType = "running" Paused StatusType = "paused" Stuck StatusType = "gurumeditation" Teleporting StatusType = "teleporting" LiveSnapshotting StatusType = "livesnapshotting" Starting StatusType = "starting" Stopping StatusType = "stopping" Saving StatusType = "saving" Restoring StatusType = "restoring" TeleportingPausedVM StatusType = "teleportingpausedvm" TeleportingIn StatusType = "teleportingin" FaultTolerantSyncing StatusType = "faulttolerantsyncing" DeletingSnapshotOnline StatusType = "deletingsnapshotlive" DeletingSnapshotPaused StatusType = "deletingsnapshotlivepaused" OnlineSnapshotting StatusType = "onlinesnapshotting" RestoringSnapshot StatusType = "restoringsnapshot" DeletingSnapshot StatusType = "deletingsnapshot" SettingUp StatusType = "settingup" Snapshotting StatusType = "snapshotting" Unknown StatusType = "unknown" // TODO: update as new VM states are added ) func IsStatusOnlineOrTransient(vmStatus StatusType) bool { switch vmStatus { case Running, Paused, Stuck, Teleporting, LiveSnapshotting, Starting, Stopping, Saving, Restoring, TeleportingPausedVM, TeleportingIn, FaultTolerantSyncing, DeletingSnapshotOnline, DeletingSnapshotPaused, OnlineSnapshotting, RestoringSnapshot, DeletingSnapshot, SettingUp, Snapshotting: return true } return false } func VboxManageOutput(exe string, args ...string) (string, error) { var stdout, stderr bytes.Buffer log.Debugf("Executing VBoxManageOutput: %#v", args) cmd := exec.Command(exe, args...) cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() stderrString := strings.TrimSpace(stderr.String()) if _, ok := err.(*exec.ExitError); ok { err = fmt.Errorf("VBoxManageOutput error: %s", stderrString) } return stdout.String(), err } func VBoxManage(args ...string) (string, error) { return VboxManageOutput("vboxmanage", args...) } func Version() (string, error) { version, err := VBoxManage("--version") if err != nil { return "", err } return strings.TrimSpace(version), nil } func FindSSHPort(vmName string) (port string, err error) { info, err := VBoxManage("showvminfo", vmName) if err != nil { return } portRe := regexp.MustCompile(`guestssh.*host port = (\d+)`) sshPort := portRe.FindStringSubmatch(info) if len(sshPort) >= 2 { port = sshPort[1] } else { err = errors.New("failed to find guestssh port") } return } func Exist(vmName string) bool { _, err := VBoxManage("showvminfo", vmName) if err != nil { return false } return true } func CreateOsVM(vmName string, templateName string, templateSnapshot string) error { args := []string{"clonevm", vmName, "--mode", "machine", "--name", templateName, "--register"} if templateSnapshot != "" { args = append(args, "--snapshot", templateSnapshot, "--options", "link") } _, err := VBoxManage(args...) return err } func isPortUnassigned(testPort string, usedPorts [][]string) bool { for _, port := range usedPorts { if testPort == port[1] { return false } } return true } func getUsedVirtualBoxPorts() (usedPorts [][]string, err error) { output, err := VBoxManage("list", "vms", "-l") if err != nil { return } allPortsRe := regexp.MustCompile(`host port = (\d+)`) usedPorts = allPortsRe.FindAllStringSubmatch(output, -1) return } func allocatePort(handler func(port string) error) (port string, err error) { ln, err := net.Listen("tcp", ":0") if err != nil { log.Debugln("VirtualBox ConfigureSSH:", err) return } defer ln.Close() usedPorts, err := getUsedVirtualBoxPorts() if err != nil { log.Debugln("VirtualBox ConfigureSSH:", err) return } addressElements := strings.Split(ln.Addr().String(), ":") port = addressElements[len(addressElements)-1] if isPortUnassigned(port, usedPorts) { err = handler(port) } else { err = os.ErrExist } return } func ConfigureSSH(vmName string, vmSSHPort string) (port string, err error) { for { port, err = allocatePort( func(port string) error { rule := fmt.Sprintf("guestssh,tcp,127.0.0.1,%s,,%s", port, vmSSHPort) _, err = VBoxManage("modifyvm", vmName, "--natpf1", rule) return err }, ) if err == nil || err != os.ErrExist { return } } } func CreateSnapshot(vmName string, snapshotName string) error { _, err := VBoxManage("snapshot", vmName, "take", snapshotName) return err } func RevertToSnapshot(vmName string) error { _, err := VBoxManage("snapshot", vmName, "restorecurrent") return err } func HasSnapshot(vmName string, snapshotName string) bool { output, err := VBoxManage("snapshot", vmName, "list", "--machinereadable") if err != nil { return false } snapshotRe := regexp.MustCompile(fmt.Sprintf(`(?m)^Snapshot(Name|UUID)[^=]*="%s"$`, regexp.QuoteMeta(snapshotName))) snapshot := snapshotRe.FindStringSubmatch(output) return snapshot != nil } func GetCurrentSnapshot(vmName string) (string, error) { output, err := VBoxManage("snapshot", vmName, "list", "--machinereadable") if err != nil { return "", err } snapshotRe := regexp.MustCompile(`(?m)^CurrentSnapshotName="([^"]*)"$`) snapshot := snapshotRe.FindStringSubmatch(output) if snapshot == nil { return "", errors.New("Failed to match current snapshot name") } return snapshot[1], nil } func Start(vmName string) error { _, err := VBoxManage("startvm", vmName, "--type", "headless") return err } func Kill(vmName string) error { _, err := VBoxManage("controlvm", vmName, "poweroff") return err } func Delete(vmName string) error { _, err := VBoxManage("unregistervm", vmName, "--delete") return err } func Status(vmName string) (StatusType, error) { output, err := VBoxManage("showvminfo", vmName, "--machinereadable") statusRe := regexp.MustCompile(`VMState="(\w+)"`) status := statusRe.FindStringSubmatch(output) if err != nil { return NotFound, err } return StatusType(status[1]), nil } func WaitForStatus(vmName string, vmStatus StatusType, seconds int) error { var status StatusType var err error for i := 0; i < seconds; i++ { status, err = Status(vmName) if err != nil { return err } if status == vmStatus { return nil } time.Sleep(time.Second) } return errors.New("VM " + vmName + " is in " + string(status) + " where it should be in " + string(vmStatus)) } func Unregister(vmName string) error { _, err := VBoxManage("unregistervm", vmName) return err }