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 }