github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/helpers/virtualbox/control.go (about)

     1  package virtualbox
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	log "github.com/Sirupsen/logrus"
     8  	"net"
     9  	"os"
    10  	"os/exec"
    11  	"regexp"
    12  	"strings"
    13  	"time"
    14  )
    15  
    16  type StatusType string
    17  
    18  const (
    19  	NotFound               StatusType = "notfound"
    20  	PoweredOff             StatusType = "poweroff"
    21  	Saved                  StatusType = "saved"
    22  	Teleported             StatusType = "teleported"
    23  	Aborted                StatusType = "aborted"
    24  	Running                StatusType = "running"
    25  	Paused                 StatusType = "paused"
    26  	Stuck                  StatusType = "gurumeditation"
    27  	Teleporting            StatusType = "teleporting"
    28  	LiveSnapshotting       StatusType = "livesnapshotting"
    29  	Starting               StatusType = "starting"
    30  	Stopping               StatusType = "stopping"
    31  	Saving                 StatusType = "saving"
    32  	Restoring              StatusType = "restoring"
    33  	TeleportingPausedVM    StatusType = "teleportingpausedvm"
    34  	TeleportingIn          StatusType = "teleportingin"
    35  	FaultTolerantSyncing   StatusType = "faulttolerantsyncing"
    36  	DeletingSnapshotOnline StatusType = "deletingsnapshotlive"
    37  	DeletingSnapshotPaused StatusType = "deletingsnapshotlivepaused"
    38  	OnlineSnapshotting     StatusType = "onlinesnapshotting"
    39  	RestoringSnapshot      StatusType = "restoringsnapshot"
    40  	DeletingSnapshot       StatusType = "deletingsnapshot"
    41  	SettingUp              StatusType = "settingup"
    42  	Snapshotting           StatusType = "snapshotting"
    43  	Unknown                StatusType = "unknown"
    44  	// TODO: update as new VM states are added
    45  )
    46  
    47  func IsStatusOnlineOrTransient(vmStatus StatusType) bool {
    48  	switch vmStatus {
    49  	case Running,
    50  		Paused,
    51  		Stuck,
    52  		Teleporting,
    53  		LiveSnapshotting,
    54  		Starting,
    55  		Stopping,
    56  		Saving,
    57  		Restoring,
    58  		TeleportingPausedVM,
    59  		TeleportingIn,
    60  		FaultTolerantSyncing,
    61  		DeletingSnapshotOnline,
    62  		DeletingSnapshotPaused,
    63  		OnlineSnapshotting,
    64  		RestoringSnapshot,
    65  		DeletingSnapshot,
    66  		SettingUp,
    67  		Snapshotting:
    68  		return true
    69  	}
    70  
    71  	return false
    72  }
    73  
    74  func VboxManageOutput(exe string, args ...string) (string, error) {
    75  	var stdout, stderr bytes.Buffer
    76  	log.Debugf("Executing VBoxManageOutput: %#v", args)
    77  	cmd := exec.Command(exe, args...)
    78  	cmd.Stdout = &stdout
    79  	cmd.Stderr = &stderr
    80  	err := cmd.Run()
    81  
    82  	stderrString := strings.TrimSpace(stderr.String())
    83  
    84  	if _, ok := err.(*exec.ExitError); ok {
    85  		err = fmt.Errorf("VBoxManageOutput error: %s", stderrString)
    86  	}
    87  
    88  	return stdout.String(), err
    89  }
    90  
    91  func VBoxManage(args ...string) (string, error) {
    92  	return VboxManageOutput("vboxmanage", args...)
    93  }
    94  
    95  func Version() (string, error) {
    96  	version, err := VBoxManage("--version")
    97  	if err != nil {
    98  		return "", err
    99  	}
   100  	return strings.TrimSpace(version), nil
   101  }
   102  
   103  func FindSSHPort(vmName string) (port string, err error) {
   104  	info, err := VBoxManage("showvminfo", vmName)
   105  	if err != nil {
   106  		return
   107  	}
   108  	portRe := regexp.MustCompile(`guestssh.*host port = (\d+)`)
   109  	sshPort := portRe.FindStringSubmatch(info)
   110  	if len(sshPort) >= 2 {
   111  		port = sshPort[1]
   112  	} else {
   113  		err = errors.New("failed to find guestssh port")
   114  	}
   115  	return
   116  }
   117  
   118  func Exist(vmName string) bool {
   119  	_, err := VBoxManage("showvminfo", vmName)
   120  	if err != nil {
   121  		return false
   122  	}
   123  	return true
   124  }
   125  
   126  func CreateOsVM(vmName string, templateName string, templateSnapshot string) error {
   127  	args := []string{"clonevm", vmName, "--mode", "machine", "--name", templateName, "--register"}
   128  	if templateSnapshot != "" {
   129  		args = append(args, "--snapshot", templateSnapshot, "--options", "link")
   130  	}
   131  	_, err := VBoxManage(args...)
   132  	return err
   133  }
   134  
   135  func isPortUnassigned(testPort string, usedPorts [][]string) bool {
   136  	for _, port := range usedPorts {
   137  		if testPort == port[1] {
   138  			return false
   139  		}
   140  	}
   141  	return true
   142  }
   143  
   144  func getUsedVirtualBoxPorts() (usedPorts [][]string, err error) {
   145  	output, err := VBoxManage("list", "vms", "-l")
   146  	if err != nil {
   147  		return
   148  	}
   149  	allPortsRe := regexp.MustCompile(`host port = (\d+)`)
   150  	usedPorts = allPortsRe.FindAllStringSubmatch(output, -1)
   151  	return
   152  }
   153  
   154  func allocatePort(handler func(port string) error) (port string, err error) {
   155  	ln, err := net.Listen("tcp", ":0")
   156  	if err != nil {
   157  		log.Debugln("VirtualBox ConfigureSSH:", err)
   158  		return
   159  	}
   160  	defer ln.Close()
   161  
   162  	usedPorts, err := getUsedVirtualBoxPorts()
   163  	if err != nil {
   164  		log.Debugln("VirtualBox ConfigureSSH:", err)
   165  		return
   166  	}
   167  
   168  	addressElements := strings.Split(ln.Addr().String(), ":")
   169  	port = addressElements[len(addressElements)-1]
   170  
   171  	if isPortUnassigned(port, usedPorts) {
   172  		err = handler(port)
   173  	} else {
   174  		err = os.ErrExist
   175  	}
   176  	return
   177  }
   178  
   179  func ConfigureSSH(vmName string, vmSSHPort string) (port string, err error) {
   180  	for {
   181  		port, err = allocatePort(
   182  			func(port string) error {
   183  				rule := fmt.Sprintf("guestssh,tcp,127.0.0.1,%s,,%s", port, vmSSHPort)
   184  				_, err = VBoxManage("modifyvm", vmName, "--natpf1", rule)
   185  				return err
   186  			},
   187  		)
   188  		if err == nil || err != os.ErrExist {
   189  			return
   190  		}
   191  	}
   192  }
   193  
   194  func CreateSnapshot(vmName string, snapshotName string) error {
   195  	_, err := VBoxManage("snapshot", vmName, "take", snapshotName)
   196  	return err
   197  }
   198  
   199  func RevertToSnapshot(vmName string) error {
   200  	_, err := VBoxManage("snapshot", vmName, "restorecurrent")
   201  	return err
   202  }
   203  
   204  func HasSnapshot(vmName string, snapshotName string) bool {
   205  	output, err := VBoxManage("snapshot", vmName, "list", "--machinereadable")
   206  	if err != nil {
   207  		return false
   208  	}
   209  	snapshotRe := regexp.MustCompile(fmt.Sprintf(`(?m)^Snapshot(Name|UUID)[^=]*="%s"$`, regexp.QuoteMeta(snapshotName)))
   210  	snapshot := snapshotRe.FindStringSubmatch(output)
   211  	return snapshot != nil
   212  }
   213  
   214  func GetCurrentSnapshot(vmName string) (string, error) {
   215  	output, err := VBoxManage("snapshot", vmName, "list", "--machinereadable")
   216  	if err != nil {
   217  		return "", err
   218  	}
   219  	snapshotRe := regexp.MustCompile(`(?m)^CurrentSnapshotName="([^"]*)"$`)
   220  	snapshot := snapshotRe.FindStringSubmatch(output)
   221  	if snapshot == nil {
   222  		return "", errors.New("Failed to match current snapshot name")
   223  	}
   224  	return snapshot[1], nil
   225  }
   226  
   227  func Start(vmName string) error {
   228  	_, err := VBoxManage("startvm", vmName, "--type", "headless")
   229  	return err
   230  }
   231  
   232  func Kill(vmName string) error {
   233  	_, err := VBoxManage("controlvm", vmName, "poweroff")
   234  	return err
   235  }
   236  
   237  func Delete(vmName string) error {
   238  	_, err := VBoxManage("unregistervm", vmName, "--delete")
   239  	return err
   240  }
   241  
   242  func Status(vmName string) (StatusType, error) {
   243  	output, err := VBoxManage("showvminfo", vmName, "--machinereadable")
   244  	statusRe := regexp.MustCompile(`VMState="(\w+)"`)
   245  	status := statusRe.FindStringSubmatch(output)
   246  	if err != nil {
   247  		return NotFound, err
   248  	}
   249  	return StatusType(status[1]), nil
   250  }
   251  
   252  func WaitForStatus(vmName string, vmStatus StatusType, seconds int) error {
   253  	var status StatusType
   254  	var err error
   255  	for i := 0; i < seconds; i++ {
   256  		status, err = Status(vmName)
   257  		if err != nil {
   258  			return err
   259  		}
   260  		if status == vmStatus {
   261  			return nil
   262  		}
   263  		time.Sleep(time.Second)
   264  	}
   265  	return errors.New("VM " + vmName + " is in " + string(status) + " where it should be in " + string(vmStatus))
   266  }
   267  
   268  func Unregister(vmName string) error {
   269  	_, err := VBoxManage("unregistervm", vmName)
   270  	return err
   271  }