diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index 0c24474ed4..7c869c4e25 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -136,7 +136,7 @@ func GetCreateFlags() []cli.Flag { } func (d *Driver) GetSSHHostname() (string, error) { - return "localhost", nil + return "127.0.0.1", nil } func (d *Driver) GetSSHUsername() string { diff --git a/libmachine/drivers/utils.go b/libmachine/drivers/utils.go index 40174fb67d..716d81eb2e 100644 --- a/libmachine/drivers/utils.go +++ b/libmachine/drivers/utils.go @@ -2,17 +2,12 @@ package drivers import ( "fmt" - "strings" "github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/ssh" ) -const ( - ErrExitCode255 = "255" -) - func GetSSHClientFromDriver(d Driver) (ssh.Client, error) { addr, err := d.GetSSHHostname() if err != nil { @@ -33,10 +28,6 @@ func GetSSHClientFromDriver(d Driver) (ssh.Client, error) { } -func isErr255Exit(err error) bool { - return strings.Contains(err.Error(), ErrExitCode255) -} - func RunSSHCommandFromDriver(d Driver, command string) (string, error) { client, err := GetSSHClientFromDriver(d) if err != nil { @@ -47,34 +38,21 @@ func RunSSHCommandFromDriver(d Driver, command string) (string, error) { output, err := client.Output(command) log.Debugf("SSH cmd err, output: %v: %s", err, output) - if err != nil && !isErr255Exit(err) { - log.Error("SSH cmd error!") - log.Errorf("command: %s", command) - log.Errorf("err : %v", err) - log.Fatalf("output : %s", output) + if err != nil { + returnedErr := fmt.Errorf(`Something went wrong running an SSH command! +command : %s +err : %v +output : %s +`, command, err, output) + return "", returnedErr } - return output, err + return output, nil } func sshAvailableFunc(d Driver) func() bool { return func() bool { log.Debug("Getting to WaitForSSH function...") - hostname, err := d.GetSSHHostname() - if err != nil { - log.Debugf("Error getting IP address waiting for SSH: %s", err) - return false - } - port, err := d.GetSSHPort() - if err != nil { - log.Debugf("Error getting SSH port: %s", err) - return false - } - if err := ssh.WaitForTCP(fmt.Sprintf("%s:%d", hostname, port)); err != nil { - log.Debugf("Error waiting for TCP waiting for SSH: %s", err) - return false - } - if _, err := RunSSHCommandFromDriver(d, "exit 0"); err != nil { log.Debugf("Error getting ssh command 'exit 0' : %s", err) return false @@ -85,7 +63,7 @@ func sshAvailableFunc(d Driver) func() bool { func WaitForSSH(d Driver) error { if err := mcnutils.WaitFor(sshAvailableFunc(d)); err != nil { - return fmt.Errorf("Too many retries. Last error: %s", err) + return fmt.Errorf("Too many retries waiting for SSH to be available. Last error: %s", err) } return nil } diff --git a/libmachine/host/host.go b/libmachine/host/host.go index 4fad586e76..521cc759d5 100644 --- a/libmachine/host/host.go +++ b/libmachine/host/host.go @@ -175,7 +175,3 @@ func (h *Host) ConfigureAuth() error { return nil } - -func WaitForSSH(h *Host) error { - return drivers.WaitForSSH(h.Driver) -} diff --git a/libmachine/libmachine.go b/libmachine/libmachine.go index cfe583f27d..bf33f28882 100644 --- a/libmachine/libmachine.go +++ b/libmachine/libmachine.go @@ -59,7 +59,7 @@ func Create(store persist.Store, h *host.Host) error { } log.Info("Machine is running, waiting for SSH to be available...") - if err := host.WaitForSSH(h); err != nil { + if err := drivers.WaitForSSH(h.Driver); err != nil { return fmt.Errorf("Error waiting for SSH: %s", err) } diff --git a/libmachine/mcnutils/utils.go b/libmachine/mcnutils/utils.go index 67d8feeb4c..c106ee2acc 100644 --- a/libmachine/mcnutils/utils.go +++ b/libmachine/mcnutils/utils.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io" - "net" "os" "runtime" "strconv" @@ -95,18 +94,6 @@ func WaitFor(f func() bool) error { return WaitForSpecific(f, 60, 3*time.Second) } -func WaitForDocker(ip string, daemonPort int) error { - return WaitFor(func() bool { - conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, daemonPort)) - if err != nil { - log.Debugf("Daemon not responding yet: %s", err) - return false - } - conn.Close() - return true - }) -} - func DumpVal(vals ...interface{}) { for _, val := range vals { prettyJSON, err := json.MarshalIndent(val, "", " ") diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index d16d90e29f..c12f16b431 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -3,8 +3,10 @@ package provision import ( "bytes" "fmt" + "net" "path" "text/template" + "time" "github.com/docker/machine/commands/mcndirs" "github.com/docker/machine/libmachine/auth" @@ -182,6 +184,35 @@ func (provisioner *Boot2DockerProvisioner) GetOsReleaseInfo() (*OsRelease, error } func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions, engineOptions engine.EngineOptions) error { + const ( + dockerPort = 2376 + ) + + defer func() { + ip, err := provisioner.Driver.GetIP() + if err != nil { + log.Fatalf("Could not get IP address for created machine: %s", err) + } + + if conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, dockerPort), 5*time.Second); err != nil { + log.Warn(` +This machine has been allocated an IP address, but Docker Machine could not +reach it successfully. + +SSH for the machine should still work, but connecting to exposed ports, such as +the Docker daemon port (usually :2376), may not work properly. + +You may need to add the route manually, or use another related workaround. + +This could be due to a VPN, proxy, or host file configuration issue. + +You also might want to clear any VirtualBox host only interfaces you are not using.`) + log.Fatal(err) + } else { + conn.Close() + } + }() + provisioner.SwarmOptions = swarmOptions provisioner.AuthOptions = authOptions provisioner.EngineOptions = engineOptions @@ -194,14 +225,9 @@ func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOpt return err } - ip, err := provisioner.GetDriver().GetIP() - if err != nil { - return err - } - // b2d hosts need to wait for the daemon to be up // before continuing with provisioning - if err := mcnutils.WaitForDocker(ip, 2376); err != nil { + if err := waitForDocker(provisioner, dockerPort); err != nil { return err } diff --git a/libmachine/provision/coreos.go b/libmachine/provision/coreos.go index 21d7247297..ab6465cf80 100644 --- a/libmachine/provision/coreos.go +++ b/libmachine/provision/coreos.go @@ -9,7 +9,6 @@ import ( "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/engine" "github.com/docker/machine/libmachine/log" - "github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/provision/pkgaction" "github.com/docker/machine/libmachine/provision/serviceaction" "github.com/docker/machine/libmachine/swarm" @@ -59,11 +58,7 @@ func (provisioner *CoreOSProvisioner) Service(name string, action serviceaction. // wait until docker is running if (name == "docker") && (action.String() == "start") { - ip, err := provisioner.GetDriver().GetIP() - if err != nil { - return err - } - if err := mcnutils.WaitForDocker(ip, 2376); err != nil { + if err := waitForDocker(provisioner, 2376); err != nil { return err } } diff --git a/libmachine/provision/errors.go b/libmachine/provision/errors.go index b3b2caa301..b2a0c09efd 100644 --- a/libmachine/provision/errors.go +++ b/libmachine/provision/errors.go @@ -2,6 +2,7 @@ package provision import ( "errors" + "fmt" ) var ( @@ -9,3 +10,17 @@ var ( ErrSSHCommandFailed = errors.New("SSH command failure") ErrNotImplemented = errors.New("Runtime not implemented") ) + +type ErrDaemonAvailable struct { + wrappedErr error +} + +func (e ErrDaemonAvailable) Error() string { + return fmt.Sprintf("Unable to verify the Docker daemon is listening: %s", e.wrappedErr) +} + +func NewErrDaemonAvailable(err error) ErrDaemonAvailable { + return ErrDaemonAvailable{ + wrappedErr: err, + } +} diff --git a/libmachine/provision/utils.go b/libmachine/provision/utils.go index bedd6dda71..6195c1dfc1 100644 --- a/libmachine/provision/utils.go +++ b/libmachine/provision/utils.go @@ -6,8 +6,10 @@ import ( "net/url" "path" "path/filepath" + "regexp" "strconv" "strings" + "time" "github.com/docker/machine/libmachine/auth" "github.com/docker/machine/libmachine/cert" @@ -58,12 +60,13 @@ func ConfigureAuth(p Provisioner) error { err error ) - machineName := p.GetDriver().GetMachineName() + driver := p.GetDriver() + machineName := driver.GetMachineName() authOptions := p.GetAuthOptions() org := machineName bits := 2048 - ip, err := p.GetDriver().GetIP() + ip, err := driver.GetIP() if err != nil { return err } @@ -143,7 +146,7 @@ func ConfigureAuth(p Provisioner) error { return err } - dockerUrl, err := p.GetDriver().GetURL() + dockerUrl, err := driver.GetURL() if err != nil { return err } @@ -176,10 +179,50 @@ func ConfigureAuth(p Provisioner) error { return err } - // TODO: Do not hardcode daemon port, ask the driver - if err := mcnutils.WaitForDocker(ip, dockerPort); err != nil { + if err := waitForDocker(p, dockerPort); err != nil { return err } return nil } + +func matchNetstatOut(reDaemonListening, netstatOut string) bool { + // TODO: I would really prefer this be a Scanner directly on + // the STDOUT of the executed command than to do all the string + // manipulation hokey-pokey. + // + // TODO: Unit test this matching. + for _, line := range strings.Split(netstatOut, "\n") { + match, err := regexp.MatchString(reDaemonListening, line) + if err != nil { + log.Warnf("Regex warning: %s", err) + } + if match && line != "" { + return true + } + } + + return false +} + +func checkDaemonUp(p Provisioner, dockerPort int) func() bool { + reDaemonListening := fmt.Sprintf(":%d.*LISTEN", dockerPort) + return func() bool { + // HACK: Check netstat's output to see if anyone's listening on the Docker API port. + netstatOut, err := p.SSHCommand("netstat -a") + if err != nil { + log.Warnf("Error running SSH command: %s", err) + return false + } + + return matchNetstatOut(reDaemonListening, netstatOut) + } +} + +func waitForDocker(p Provisioner, dockerPort int) error { + if err := mcnutils.WaitForSpecific(checkDaemonUp(p, dockerPort), 5, 3*time.Second); err != nil { + return NewErrDaemonAvailable(err) + } + + return nil +} diff --git a/libmachine/provision/utils_test.go b/libmachine/provision/utils_test.go index 10617e638d..60c33349d1 100644 --- a/libmachine/provision/utils_test.go +++ b/libmachine/provision/utils_test.go @@ -10,6 +10,50 @@ import ( "github.com/docker/machine/libmachine/auth" ) +var ( + reDaemonListening = ":2376.*LISTEN" +) + +func TestMatchNetstatOutMissing(t *testing.T) { + nsOut := `Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN +tcp 0 72 192.168.25.141:ssh 192.168.25.1:63213 ESTABLISHED +tcp 0 0 :::ssh :::* LISTEN +Active UNIX domain sockets (servers and established) +Proto RefCnt Flags Type State I-Node Path +unix 2 [ ACC ] STREAM LISTENING 17990 /var/run/acpid.socket +unix 2 [ ACC ] SEQPACKET LISTENING 14233 /run/udev/control +unix 3 [ ] STREAM CONNECTED 18688 +unix 3 [ ] DGRAM 14243 +unix 3 [ ] STREAM CONNECTED 18689 +unix 3 [ ] DGRAM 14242` + if matchNetstatOut(reDaemonListening, nsOut) { + t.Fatal("Expected not to match the netstat output as showing the daemon listening but got a match") + } +} + +func TestMatchNetstatOutPresent(t *testing.T) { + nsOut := `Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN +tcp 0 72 192.168.25.141:ssh 192.168.25.1:63235 ESTABLISHED +tcp 0 0 :::2376 :::* LISTEN +tcp 0 0 :::ssh :::* LISTEN +Active UNIX domain sockets (servers and established) +Proto RefCnt Flags Type State I-Node Path +unix 2 [ ACC ] STREAM LISTENING 17990 /var/run/acpid.socket +unix 2 [ ACC ] SEQPACKET LISTENING 14233 /run/udev/control +unix 2 [ ACC ] STREAM LISTENING 19365 /var/run/docker.sock +unix 3 [ ] STREAM CONNECTED 19774 +unix 3 [ ] STREAM CONNECTED 19775 +unix 3 [ ] DGRAM 14243 +unix 3 [ ] DGRAM 14242` + if !matchNetstatOut(reDaemonListening, nsOut) { + t.Fatal("Expected to match the netstat output as showing the daemon listening but didn't") + } +} + func TestGenerateDockerOptionsBoot2Docker(t *testing.T) { p := &Boot2DockerProvisioner{ Driver: &fakedriver.FakeDriver{}, diff --git a/libmachine/ssh/ssh.go b/libmachine/ssh/ssh.go deleted file mode 100644 index e85c981738..0000000000 --- a/libmachine/ssh/ssh.go +++ /dev/null @@ -1,22 +0,0 @@ -package ssh - -import ( - "net" - "time" - - "github.com/docker/machine/libmachine/log" -) - -func WaitForTCP(addr string) error { - for { - log.Debugf("Testing TCP connection to: %s", addr) - conn, err := net.DialTimeout("tcp", addr, 2*time.Second) - - if err != nil { - continue - } - - defer conn.Close() - return nil - } -}