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 }