mirror of https://github.com/docker/docs.git
Merge pull request #2172 from dgageot/2154-query-virtualbox-serially
FIX #2154 query virtualbox serially
This commit is contained in:
commit
bc7da2b0fa
|
@ -69,6 +69,10 @@ func newPluginDriver(driverName string, rawContent []byte) (drivers.Driver, erro
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if driverName == "virtualbox" {
|
||||
return drivers.NewSerialDriver(d), nil
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
|
@ -392,35 +396,13 @@ func machineCommand(actionName string, host *host.Host, errorChan chan<- error)
|
|||
func runActionForeachMachine(actionName string, machines []*host.Host) []error {
|
||||
var (
|
||||
numConcurrentActions = 0
|
||||
serialMachines = []*host.Host{}
|
||||
errorChan = make(chan error)
|
||||
errs = []error{}
|
||||
)
|
||||
|
||||
for _, machine := range machines {
|
||||
// Virtualbox is temperamental about doing things concurrently,
|
||||
// so we schedule the actions in a "queue" to be executed serially
|
||||
// after the concurrent actions are scheduled.
|
||||
switch machine.DriverName {
|
||||
case "virtualbox":
|
||||
machine := machine
|
||||
serialMachines = append(serialMachines, machine)
|
||||
default:
|
||||
numConcurrentActions++
|
||||
go machineCommand(actionName, machine, errorChan)
|
||||
}
|
||||
}
|
||||
|
||||
// While the concurrent actions are running,
|
||||
// do the serial actions. As the name implies,
|
||||
// these run one at a time.
|
||||
for _, machine := range serialMachines {
|
||||
serialChan := make(chan error)
|
||||
go machineCommand(actionName, machine, serialChan)
|
||||
if err := <-serialChan; err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
close(serialChan)
|
||||
numConcurrentActions++
|
||||
go machineCommand(actionName, machine, errorChan)
|
||||
}
|
||||
|
||||
// TODO: We should probably only do 5-10 of these
|
||||
|
|
|
@ -320,6 +320,10 @@ func cmdCreateOuter(c CommandLine) error {
|
|||
}
|
||||
}
|
||||
|
||||
if serialDriver, ok := driver.(*drivers.SerialDriver); ok {
|
||||
driver = serialDriver.Driver
|
||||
}
|
||||
|
||||
if rpcd, ok := driver.(*rpcdriver.RpcClientDriver); ok {
|
||||
if err := rpcd.Close(); err != nil {
|
||||
return err
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
package drivers
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
||||
var stdLock = &sync.Mutex{}
|
||||
|
||||
// Some providers, e.g. virtualbox, should not run driver operations at the
|
||||
// same time as other driver instances of the same type. Otherwise, we scrape
|
||||
// up against VirtualBox's own locking mechanisms. Therefore this is a wrapper
|
||||
// struct which is used to ensure that RPC calls to these drivers only occur
|
||||
// one at a time.
|
||||
//
|
||||
// It would be preferable to simply have a lock around, say, the VBoxManage
|
||||
// command, but with our current one-server-process-per-machine model it is
|
||||
// impossible to dictate this locking on the server side.
|
||||
type SerialDriver struct {
|
||||
Driver
|
||||
sync.Locker
|
||||
}
|
||||
|
||||
func NewSerialDriver(innerDriver Driver) Driver {
|
||||
return newSerialDriverWithLock(innerDriver, stdLock)
|
||||
}
|
||||
|
||||
func newSerialDriverWithLock(innerDriver Driver, lock sync.Locker) Driver {
|
||||
return &SerialDriver{
|
||||
Driver: innerDriver,
|
||||
Locker: lock,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a host using the driver's config
|
||||
func (d *SerialDriver) Create() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.Create()
|
||||
}
|
||||
|
||||
// DriverName returns the name of the driver as it is registered
|
||||
func (d *SerialDriver) DriverName() string {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.DriverName()
|
||||
}
|
||||
|
||||
// GetCreateFlags returns the mcnflag.Flag slice representing the flags
|
||||
// that can be set, their descriptions and defaults.
|
||||
func (d *SerialDriver) GetCreateFlags() []mcnflag.Flag {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.GetCreateFlags()
|
||||
}
|
||||
|
||||
// GetIP returns an IP or hostname that this host is available at
|
||||
// e.g. 1.2.3.4 or docker-host-d60b70a14d3a.cloudapp.net
|
||||
func (d *SerialDriver) GetIP() (string, error) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.GetIP()
|
||||
}
|
||||
|
||||
// GetMachineName returns the name of the machine
|
||||
func (d *SerialDriver) GetMachineName() string {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.GetMachineName()
|
||||
}
|
||||
|
||||
// GetSSHHostname returns hostname for use with ssh
|
||||
func (d *SerialDriver) GetSSHHostname() (string, error) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.GetSSHHostname()
|
||||
}
|
||||
|
||||
// GetSSHKeyPath returns key path for use with ssh
|
||||
func (d *SerialDriver) GetSSHKeyPath() string {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.GetSSHKeyPath()
|
||||
}
|
||||
|
||||
// GetSSHPort returns port for use with ssh
|
||||
func (d *SerialDriver) GetSSHPort() (int, error) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.GetSSHPort()
|
||||
}
|
||||
|
||||
// GetSSHUsername returns username for use with ssh
|
||||
func (d *SerialDriver) GetSSHUsername() string {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.GetSSHUsername()
|
||||
}
|
||||
|
||||
// GetURL returns a Docker compatible host URL for connecting to this host
|
||||
// e.g. tcp://1.2.3.4:2376
|
||||
func (d *SerialDriver) GetURL() (string, error) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.GetURL()
|
||||
}
|
||||
|
||||
// GetState returns the state that the host is in (running, stopped, etc)
|
||||
func (d *SerialDriver) GetState() (state.State, error) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.GetState()
|
||||
}
|
||||
|
||||
// Kill stops a host forcefully
|
||||
func (d *SerialDriver) Kill() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.Kill()
|
||||
}
|
||||
|
||||
// PreCreateCheck allows for pre-create operations to make sure a driver is ready for creation
|
||||
func (d *SerialDriver) PreCreateCheck() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.PreCreateCheck()
|
||||
}
|
||||
|
||||
// Remove a host
|
||||
func (d *SerialDriver) Remove() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.Remove()
|
||||
}
|
||||
|
||||
// Restart a host. This may just call Stop(); Start() if the provider does not
|
||||
// have any special restart behaviour.
|
||||
func (d *SerialDriver) Restart() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.Restart()
|
||||
}
|
||||
|
||||
// SetConfigFromFlags configures the driver with the object that was returned
|
||||
// by RegisterCreateFlags
|
||||
func (d *SerialDriver) SetConfigFromFlags(opts DriverOptions) error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.SetConfigFromFlags(opts)
|
||||
}
|
||||
|
||||
// Start a host
|
||||
func (d *SerialDriver) Start() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.Start()
|
||||
}
|
||||
|
||||
// Stop a host gracefully
|
||||
func (d *SerialDriver) Stop() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.Driver.Stop()
|
||||
}
|
|
@ -0,0 +1,296 @@
|
|||
package drivers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type CallRecorder struct {
|
||||
calls []string
|
||||
}
|
||||
|
||||
func (c *CallRecorder) record(call string) {
|
||||
c.calls = append(c.calls, call)
|
||||
}
|
||||
|
||||
type MockLocker struct {
|
||||
calls *CallRecorder
|
||||
}
|
||||
|
||||
func (l *MockLocker) Lock() {
|
||||
l.calls.record("Lock")
|
||||
}
|
||||
|
||||
func (l *MockLocker) Unlock() {
|
||||
l.calls.record("Unlock")
|
||||
}
|
||||
|
||||
type MockDriver struct {
|
||||
calls *CallRecorder
|
||||
driverName string
|
||||
flags []mcnflag.Flag
|
||||
ip string
|
||||
machineName string
|
||||
sshHostname string
|
||||
sshKeyPath string
|
||||
sshPort int
|
||||
sshUsername string
|
||||
url string
|
||||
state state.State
|
||||
}
|
||||
|
||||
func (d *MockDriver) Create() error {
|
||||
d.calls.record("Create")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) DriverName() string {
|
||||
d.calls.record("DriverName")
|
||||
return d.driverName
|
||||
}
|
||||
|
||||
func (d *MockDriver) GetCreateFlags() []mcnflag.Flag {
|
||||
d.calls.record("GetCreateFlags")
|
||||
return d.flags
|
||||
}
|
||||
|
||||
func (d *MockDriver) GetIP() (string, error) {
|
||||
d.calls.record("GetIP")
|
||||
return d.ip, nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) GetMachineName() string {
|
||||
d.calls.record("GetMachineName")
|
||||
return d.machineName
|
||||
}
|
||||
|
||||
func (d *MockDriver) GetSSHHostname() (string, error) {
|
||||
d.calls.record("GetSSHHostname")
|
||||
return d.sshHostname, nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) GetSSHKeyPath() string {
|
||||
d.calls.record("GetSSHKeyPath")
|
||||
return d.sshKeyPath
|
||||
}
|
||||
|
||||
func (d *MockDriver) GetSSHPort() (int, error) {
|
||||
d.calls.record("GetSSHPort")
|
||||
return d.sshPort, nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) GetSSHUsername() string {
|
||||
d.calls.record("GetSSHUsername")
|
||||
return d.sshUsername
|
||||
}
|
||||
|
||||
func (d *MockDriver) GetURL() (string, error) {
|
||||
d.calls.record("GetURL")
|
||||
return d.url, nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) GetState() (state.State, error) {
|
||||
d.calls.record("GetState")
|
||||
return d.state, nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) Kill() error {
|
||||
d.calls.record("Kill")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) PreCreateCheck() error {
|
||||
d.calls.record("PreCreateCheck")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) Remove() error {
|
||||
d.calls.record("Remove")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) Restart() error {
|
||||
d.calls.record("Restart")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) SetConfigFromFlags(opts DriverOptions) error {
|
||||
d.calls.record("SetConfigFromFlags")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) Start() error {
|
||||
d.calls.record("Start")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *MockDriver) Stop() error {
|
||||
d.calls.record("Stop")
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSerialDriverCreate(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
err := driver.Create()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"Lock", "Create", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverDriverName(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{driverName: "DRIVER", calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
driverName := driver.DriverName()
|
||||
|
||||
assert.Equal(t, "DRIVER", driverName)
|
||||
assert.Equal(t, []string{"Lock", "DriverName", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverGetIP(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{ip: "IP", calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
ip, _ := driver.GetIP()
|
||||
|
||||
assert.Equal(t, "IP", ip)
|
||||
assert.Equal(t, []string{"Lock", "GetIP", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverGetMachineName(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{machineName: "MACHINE_NAME", calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
machineName := driver.GetMachineName()
|
||||
|
||||
assert.Equal(t, "MACHINE_NAME", machineName)
|
||||
assert.Equal(t, []string{"Lock", "GetMachineName", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverGetSSHHostname(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{sshHostname: "SSH_HOSTNAME", calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
sshHostname, _ := driver.GetSSHHostname()
|
||||
|
||||
assert.Equal(t, "SSH_HOSTNAME", sshHostname)
|
||||
assert.Equal(t, []string{"Lock", "GetSSHHostname", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverGetSSHKeyPath(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{sshKeyPath: "PATH", calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
path := driver.GetSSHKeyPath()
|
||||
|
||||
assert.Equal(t, "PATH", path)
|
||||
assert.Equal(t, []string{"Lock", "GetSSHKeyPath", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverGetSSHPort(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{sshPort: 42, calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
sshPort, _ := driver.GetSSHPort()
|
||||
|
||||
assert.Equal(t, 42, sshPort)
|
||||
assert.Equal(t, []string{"Lock", "GetSSHPort", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverGetSSHUsername(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{sshUsername: "SSH_USER", calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
sshUsername := driver.GetSSHUsername()
|
||||
|
||||
assert.Equal(t, "SSH_USER", sshUsername)
|
||||
assert.Equal(t, []string{"Lock", "GetSSHUsername", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverGetURL(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{url: "URL", calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
url, _ := driver.GetURL()
|
||||
|
||||
assert.Equal(t, "URL", url)
|
||||
assert.Equal(t, []string{"Lock", "GetURL", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverGetState(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{state: state.Running, calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
machineState, _ := driver.GetState()
|
||||
|
||||
assert.Equal(t, state.Running, machineState)
|
||||
assert.Equal(t, []string{"Lock", "GetState", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverKill(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
driver.Kill()
|
||||
|
||||
assert.Equal(t, []string{"Lock", "Kill", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverPreCreateCheck(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
driver.PreCreateCheck()
|
||||
|
||||
assert.Equal(t, []string{"Lock", "PreCreateCheck", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverRemove(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
driver.Remove()
|
||||
|
||||
assert.Equal(t, []string{"Lock", "Remove", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverRestart(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
driver.Restart()
|
||||
|
||||
assert.Equal(t, []string{"Lock", "Restart", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverSetConfigFromFlags(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
driver.SetConfigFromFlags(nil)
|
||||
|
||||
assert.Equal(t, []string{"Lock", "SetConfigFromFlags", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverStart(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
driver.Start()
|
||||
|
||||
assert.Equal(t, []string{"Lock", "Start", "Unlock"}, callRecorder.calls)
|
||||
}
|
||||
|
||||
func TestSerialDriverStop(t *testing.T) {
|
||||
callRecorder := &CallRecorder{}
|
||||
|
||||
driver := newSerialDriverWithLock(&MockDriver{calls: callRecorder}, &MockLocker{calls: callRecorder})
|
||||
driver.Stop()
|
||||
|
||||
assert.Equal(t, []string{"Lock", "Stop", "Unlock"}, callRecorder.calls)
|
||||
}
|
|
@ -34,6 +34,16 @@ func (s Filestore) saveToFile(data []byte, file string) error {
|
|||
}
|
||||
|
||||
func (s Filestore) Save(host *host.Host) error {
|
||||
if serialDriver, ok := host.Driver.(*drivers.SerialDriver); ok {
|
||||
// Unwrap Driver
|
||||
host.Driver = serialDriver.Driver
|
||||
|
||||
// Re-wrap Driver when done
|
||||
defer func() {
|
||||
host.Driver = serialDriver
|
||||
}()
|
||||
}
|
||||
|
||||
// TODO: Does this belong here?
|
||||
if rpcClientDriver, ok := host.Driver.(*rpcdriver.RpcClientDriver); ok {
|
||||
data, err := rpcClientDriver.GetConfigRaw()
|
||||
|
|
Loading…
Reference in New Issue