mirror of https://github.com/docker/docs.git
Merge pull request #2763 from dgageot/retry-vbox
Retry VirtualBox commands
This commit is contained in:
commit
c688617f75
|
@ -11,9 +11,17 @@ import (
|
||||||
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/machine/libmachine/log"
|
"github.com/docker/machine/libmachine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
retryCountOnObjectNotReadyError = 5
|
||||||
|
objectNotReady = "error: The object is not ready"
|
||||||
|
retryDelay = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
reColonLine = regexp.MustCompile(`(.+):\s+(.*)`)
|
reColonLine = regexp.MustCompile(`(.+):\s+(.*)`)
|
||||||
reEqualLine = regexp.MustCompile(`(.+)=(.*)`)
|
reEqualLine = regexp.MustCompile(`(.+)=(.*)`)
|
||||||
|
@ -36,7 +44,16 @@ type VBoxManager interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VBoxCmdManager communicates with VirtualBox through the commandline using `VBoxManage`.
|
// VBoxCmdManager communicates with VirtualBox through the commandline using `VBoxManage`.
|
||||||
type VBoxCmdManager struct{}
|
type VBoxCmdManager struct {
|
||||||
|
runCmd func(cmd *exec.Cmd) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVBoxManager creates a VBoxManager instance.
|
||||||
|
func NewVBoxManager() *VBoxCmdManager {
|
||||||
|
return &VBoxCmdManager{
|
||||||
|
runCmd: func(cmd *exec.Cmd) error { return cmd.Run() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (v *VBoxCmdManager) vbm(args ...string) error {
|
func (v *VBoxCmdManager) vbm(args ...string) error {
|
||||||
_, _, err := v.vbmOutErr(args...)
|
_, _, err := v.vbmOutErr(args...)
|
||||||
|
@ -49,13 +66,17 @@ func (v *VBoxCmdManager) vbmOut(args ...string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VBoxCmdManager) vbmOutErr(args ...string) (string, string, error) {
|
func (v *VBoxCmdManager) vbmOutErr(args ...string) (string, string, error) {
|
||||||
|
return v.vbmOutErrRetry(retryCountOnObjectNotReadyError, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VBoxCmdManager) vbmOutErrRetry(retry int, args ...string) (string, string, error) {
|
||||||
cmd := exec.Command(vboxManageCmd, args...)
|
cmd := exec.Command(vboxManageCmd, args...)
|
||||||
log.Debugf("COMMAND: %v %v", vboxManageCmd, strings.Join(args, " "))
|
log.Debugf("COMMAND: %v %v", vboxManageCmd, strings.Join(args, " "))
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
err := cmd.Run()
|
err := v.runCmd(cmd)
|
||||||
stderrStr := stderr.String()
|
stderrStr := stderr.String()
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
log.Debugf("STDOUT:\n{\n%v}", stdout.String())
|
log.Debugf("STDOUT:\n{\n%v}", stdout.String())
|
||||||
|
@ -68,6 +89,14 @@ func (v *VBoxCmdManager) vbmOutErr(args ...string) (string, string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sometimes, we just need to retry...
|
||||||
|
if retry > 1 {
|
||||||
|
if strings.Contains(stderrStr, objectNotReady) {
|
||||||
|
time.Sleep(retryDelay)
|
||||||
|
return v.vbmOutErrRetry(retry-1, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err == nil || strings.HasPrefix(err.Error(), "exit status ") {
|
if err == nil || strings.HasPrefix(err.Error(), "exit status ") {
|
||||||
// VBoxManage will sometimes not set the return code, but has a fatal error
|
// VBoxManage will sometimes not set the return code, but has a fatal error
|
||||||
// such as VBoxManage.exe: error: VT-x is not available. (VERR_VMX_NO_VMX)
|
// such as VBoxManage.exe: error: VT-x is not available. (VERR_VMX_NO_VMX)
|
||||||
|
|
|
@ -3,6 +3,12 @@ package virtualbox
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,3 +49,94 @@ func TestInvalidCheckVBoxManageVersion(t *testing.T) {
|
||||||
assert.EqualError(t, err, test.expectedError)
|
assert.EqualError(t, err, test.expectedError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVbmOutErr(t *testing.T) {
|
||||||
|
var cmdRun *exec.Cmd
|
||||||
|
vBoxManager := NewVBoxManager()
|
||||||
|
vBoxManager.runCmd = func(cmd *exec.Cmd) error {
|
||||||
|
cmdRun = cmd
|
||||||
|
fmt.Fprint(cmd.Stdout, "Printed to StdOut")
|
||||||
|
fmt.Fprint(cmd.Stderr, "Printed to StdErr")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stdOut, stdErr, err := vBoxManager.vbmOutErr("arg1", "arg2")
|
||||||
|
|
||||||
|
assert.Equal(t, []string{vboxManageCmd, "arg1", "arg2"}, cmdRun.Args)
|
||||||
|
assert.Equal(t, "Printed to StdOut", stdOut)
|
||||||
|
assert.Equal(t, "Printed to StdErr", stdErr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVbmOutErrError(t *testing.T) {
|
||||||
|
vBoxManager := NewVBoxManager()
|
||||||
|
vBoxManager.runCmd = func(cmd *exec.Cmd) error { return errors.New("BUG") }
|
||||||
|
|
||||||
|
_, _, err := vBoxManager.vbmOutErr("arg1", "arg2")
|
||||||
|
|
||||||
|
assert.EqualError(t, err, "BUG")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVbmOutErrNotFound(t *testing.T) {
|
||||||
|
vBoxManager := NewVBoxManager()
|
||||||
|
vBoxManager.runCmd = func(cmd *exec.Cmd) error { return &exec.Error{Err: exec.ErrNotFound} }
|
||||||
|
|
||||||
|
_, _, err := vBoxManager.vbmOutErr("arg1", "arg2")
|
||||||
|
|
||||||
|
assert.Equal(t, ErrVBMNotFound, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVbmOutErrFailingWithExitStatus(t *testing.T) {
|
||||||
|
vBoxManager := NewVBoxManager()
|
||||||
|
vBoxManager.runCmd = func(cmd *exec.Cmd) error {
|
||||||
|
fmt.Fprint(cmd.Stderr, "error: Unable to run vbox")
|
||||||
|
return errors.New("exit status BUG")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := vBoxManager.vbmOutErr("arg1", "arg2", "arg3")
|
||||||
|
|
||||||
|
assert.EqualError(t, err, vboxManageCmd+" arg1 arg2 arg3 failed:\nerror: Unable to run vbox")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVbmOutErrRetryOnce(t *testing.T) {
|
||||||
|
var cmdRun *exec.Cmd
|
||||||
|
var runCount int
|
||||||
|
vBoxManager := NewVBoxManager()
|
||||||
|
vBoxManager.runCmd = func(cmd *exec.Cmd) error {
|
||||||
|
cmdRun = cmd
|
||||||
|
|
||||||
|
runCount++
|
||||||
|
if runCount == 1 {
|
||||||
|
fmt.Fprint(cmd.Stderr, "error: The object is not ready")
|
||||||
|
return errors.New("Fail the first time it's called")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(cmd.Stdout, "Printed to StdOut")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stdOut, stdErr, err := vBoxManager.vbmOutErr("command", "arg")
|
||||||
|
|
||||||
|
assert.Equal(t, 2, runCount)
|
||||||
|
assert.Equal(t, []string{vboxManageCmd, "command", "arg"}, cmdRun.Args)
|
||||||
|
assert.Equal(t, "Printed to StdOut", stdOut)
|
||||||
|
assert.Empty(t, stdErr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVbmOutErrRetryMax(t *testing.T) {
|
||||||
|
var runCount int
|
||||||
|
vBoxManager := NewVBoxManager()
|
||||||
|
vBoxManager.runCmd = func(cmd *exec.Cmd) error {
|
||||||
|
runCount++
|
||||||
|
fmt.Fprint(cmd.Stderr, "error: The object is not ready")
|
||||||
|
return errors.New("Always fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
stdOut, stdErr, err := vBoxManager.vbmOutErr("command", "arg")
|
||||||
|
|
||||||
|
assert.Equal(t, 5, runCount)
|
||||||
|
assert.Empty(t, stdOut)
|
||||||
|
assert.Equal(t, "error: The object is not ready", stdErr)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ type Driver struct {
|
||||||
// NewDriver creates a new VirtualBox driver with default settings.
|
// NewDriver creates a new VirtualBox driver with default settings.
|
||||||
func NewDriver(hostName, storePath string) *Driver {
|
func NewDriver(hostName, storePath string) *Driver {
|
||||||
return &Driver{
|
return &Driver{
|
||||||
VBoxManager: &VBoxCmdManager{},
|
VBoxManager: NewVBoxManager(),
|
||||||
BaseDriver: &drivers.BaseDriver{
|
BaseDriver: &drivers.BaseDriver{
|
||||||
MachineName: hostName,
|
MachineName: hostName,
|
||||||
StorePath: storePath,
|
StorePath: storePath,
|
||||||
|
|
Loading…
Reference in New Issue