PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/helpers/virtualbox/control.go

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