github.com/ssube/gitlab-ci-multi-runner@v1.2.1-0.20160607142738-b8d1285632e6/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                        = "poweroff"
    21  	Saved                             = "saved"
    22  	Teleported                        = "teleported"
    23  	Aborted                           = "aborted"
    24  	Running                           = "running"
    25  	Paused                            = "paused"
    26  	Stuck                             = "gurumeditation"
    27  	Teleporting                       = "teleporting"
    28  	LiveSnapshotting                  = "livesnapshotting"
    29  	Starting                          = "starting"
    30  	Stopping                          = "stopping"
    31  	Saving                            = "saving"
    32  	Restoring                         = "restoring"
    33  	TeleportingPausedVM               = "teleportingpausedvm"
    34  	TeleportingIn                     = "teleportingin"
    35  	FaultTolerantSyncing              = "faulttolerantsyncing"
    36  	DeletingSnapshotOnline            = "deletingsnapshotlive"
    37  	DeletingSnapshotPaused            = "deletingsnapshotlivepaused"
    38  	OnlineSnapshotting                = "onlinesnapshotting"
    39  	RestoringSnapshot                 = "restoringsnapshot"
    40  	DeletingSnapshot                  = "deletingsnapshot"
    41  	SettingUp                         = "settingup"
    42  	Snapshotting                      = "snapshotting"
    43  	Unknown                           = "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) error {
   127  	_, err := VBoxManage("clonevm", vmName, "--mode", "machine", "--name", templateName, "--register")
   128  	return err
   129  }
   130  
   131  func isPortUnassigned(testPort string, usedPorts [][]string) bool {
   132  	for _, port := range usedPorts {
   133  		if testPort == port[1] {
   134  			return false
   135  		}
   136  	}
   137  	return true
   138  }
   139  
   140  func getUsedVirtualBoxPorts() (usedPorts [][]string, err error) {
   141  	output, err := VBoxManage("list", "vms", "-l")
   142  	if err != nil {
   143  		return
   144  	}
   145  	allPortsRe := regexp.MustCompile(`host port = (\d+)`)
   146  	usedPorts = allPortsRe.FindAllStringSubmatch(output, -1)
   147  	return
   148  }
   149  
   150  func allocatePort(handler func(port string) error) (port string, err error) {
   151  	ln, err := net.Listen("tcp", ":0")
   152  	if err != nil {
   153  		log.Debugln("VirtualBox ConfigureSSH:", err)
   154  		return
   155  	}
   156  	defer ln.Close()
   157  
   158  	usedPorts, err := getUsedVirtualBoxPorts()
   159  	if err != nil {
   160  		log.Debugln("VirtualBox ConfigureSSH:", err)
   161  		return
   162  	}
   163  
   164  	addressElements := strings.Split(ln.Addr().String(), ":")
   165  	port = addressElements[len(addressElements)-1]
   166  
   167  	if isPortUnassigned(port, usedPorts) {
   168  		err = handler(port)
   169  	} else {
   170  		err = os.ErrExist
   171  	}
   172  	return
   173  }
   174  
   175  func ConfigureSSH(vmName string, vmSSHPort string) (port string, err error) {
   176  	for {
   177  		port, err = allocatePort(
   178  			func(port string) error {
   179  				rule := fmt.Sprintf("guestssh,tcp,127.0.0.1,%s,,%s", port, vmSSHPort)
   180  				_, err = VBoxManage("modifyvm", vmName, "--natpf1", rule)
   181  				return err
   182  			},
   183  		)
   184  		if err == nil || err != os.ErrExist {
   185  			return
   186  		}
   187  	}
   188  }
   189  
   190  func CreateSnapshot(vmName string, snapshotName string) error {
   191  	_, err := VBoxManage("snapshot", vmName, "take", snapshotName)
   192  	return err
   193  }
   194  
   195  func RevertToSnapshot(vmName string) error {
   196  	_, err := VBoxManage("snapshot", vmName, "restorecurrent")
   197  	return err
   198  }
   199  
   200  func Start(vmName string) error {
   201  	_, err := VBoxManage("startvm", vmName, "--type", "headless")
   202  	return err
   203  }
   204  
   205  func Stop(vmName string) error {
   206  	_, err := VBoxManage("controlvm", vmName, "poweroff")
   207  	return err
   208  }
   209  
   210  func Kill(vmName string) error {
   211  	_, err := VBoxManage("controlvm", vmName, "acpipowerbutton")
   212  	return err
   213  }
   214  
   215  func Delete(vmName string) error {
   216  	_, err := VBoxManage("unregistervm", vmName, "--delete")
   217  	return err
   218  }
   219  
   220  func Status(vmName string) (StatusType, error) {
   221  	output, err := VBoxManage("showvminfo", vmName, "--machinereadable")
   222  	statusRe := regexp.MustCompile(`VMState="(\w+)"`)
   223  	status := statusRe.FindStringSubmatch(output)
   224  	if err != nil {
   225  		return NotFound, err
   226  	}
   227  	return StatusType(status[1]), nil
   228  }
   229  
   230  func WaitForStatus(vmName string, vmStatus StatusType, seconds int) error {
   231  	var status StatusType
   232  	var err error
   233  	for i := 0; i < seconds; i++ {
   234  		status, err = Status(vmName)
   235  		if err != nil {
   236  			return err
   237  		}
   238  		if status == vmStatus {
   239  			return nil
   240  		}
   241  		time.Sleep(time.Second)
   242  	}
   243  	return errors.New("VM " + vmName + " is in " + string(status) + " where it should be in " + string(vmStatus))
   244  }
   245  
   246  func Unregister(vmName string) error {
   247  	_, err := VBoxManage("unregistervm", vmName)
   248  	return err
   249  }