PageRenderTime 143ms CodeModel.GetById 61ms RepoModel.GetById 1ms app.codeStats 0ms

/helpers/virtualbox/control.go

https://gitlab.com/HeeroYui/gitlab-ci-multi-runner
Go | 276 lines | 243 code | 32 blank | 1 comment | 43 complexity | 63afc089729f1c83db2eae129710cab0 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 Stop(vmName string) error {
  206. _, err := VBoxManage("controlvm", vmName, "poweroff")
  207. return err
  208. }
  209. func Kill(vmName string) error {
  210. _, err := VBoxManage("controlvm", vmName, "acpipowerbutton")
  211. return err
  212. }
  213. func Delete(vmName string) error {
  214. _, err := VBoxManage("unregistervm", vmName, "--delete")
  215. return err
  216. }
  217. func Status(vmName string) (StatusType, error) {
  218. output, err := VBoxManage("showvminfo", vmName, "--machinereadable")
  219. statusRe := regexp.MustCompile(`VMState="(\w+)"`)
  220. status := statusRe.FindStringSubmatch(output)
  221. if err != nil {
  222. return NotFound, err
  223. }
  224. return StatusType(status[1]), nil
  225. }
  226. func WaitForStatus(vmName string, vmStatus StatusType, seconds int) error {
  227. var status StatusType
  228. var err error
  229. for i := 0; i < seconds; i++ {
  230. status, err = Status(vmName)
  231. if err != nil {
  232. return err
  233. }
  234. if status == vmStatus {
  235. return nil
  236. }
  237. time.Sleep(time.Second)
  238. }
  239. return errors.New("VM " + vmName + " is in " + string(status) + " where it should be in " + string(vmStatus))
  240. }
  241. func Unregister(vmName string) error {
  242. _, err := VBoxManage("unregistervm", vmName)
  243. return err
  244. }