Commit de9c4ace authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

builder/amazonebs: Timeouts while waiting for SSH to connect

parent 59b59026
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log" "log"
"time"
) )
// The unique ID for this builder // The unique ID for this builder
...@@ -30,9 +31,12 @@ type config struct { ...@@ -30,9 +31,12 @@ type config struct {
InstanceType string `mapstructure:"instance_type"` InstanceType string `mapstructure:"instance_type"`
SSHUsername string `mapstructure:"ssh_username"` SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"` SSHPort int `mapstructure:"ssh_port"`
SSHTimeout time.Duration
// Configuration of the resulting AMI // Configuration of the resulting AMI
AMIName string `mapstructure:"ami_name"` AMIName string `mapstructure:"ami_name"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
} }
type Builder struct { type Builder struct {
...@@ -40,16 +44,20 @@ type Builder struct { ...@@ -40,16 +44,20 @@ type Builder struct {
runner multistep.Runner runner multistep.Runner
} }
func (b *Builder) Prepare(raw interface{}) (err error) { func (b *Builder) Prepare(raw interface{}) error {
err = mapstructure.Decode(raw, &b.config) err := mapstructure.Decode(raw, &b.config)
if err != nil { if err != nil {
return return err
} }
if b.config.SSHPort == 0 { if b.config.SSHPort == 0 {
b.config.SSHPort = 22 b.config.SSHPort = 22
} }
if b.config.RawSSHTimeout == "" {
b.config.RawSSHTimeout = "1m"
}
// Accumulate any errors // Accumulate any errors
errs := make([]error, 0) errs := make([]error, 0)
...@@ -79,12 +87,17 @@ func (b *Builder) Prepare(raw interface{}) (err error) { ...@@ -79,12 +87,17 @@ func (b *Builder) Prepare(raw interface{}) (err error) {
errs = append(errs, errors.New("An ssh_username must be specified")) errs = append(errs, errors.New("An ssh_username must be specified"))
} }
b.config.SSHTimeout, err = time.ParseDuration(b.config.RawSSHTimeout)
if err != nil {
errs = append(errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err))
}
if len(errs) > 0 { if len(errs) > 0 {
return &packer.MultiError{errs} return &packer.MultiError{errs}
} }
log.Printf("Config: %+v", b.config) log.Printf("Config: %+v", b.config)
return return nil
} }
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) packer.Artifact { func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) packer.Artifact {
......
...@@ -191,6 +191,25 @@ func TestBuilderPrepare_SSHPort(t *testing.T) { ...@@ -191,6 +191,25 @@ func TestBuilderPrepare_SSHPort(t *testing.T) {
} }
} }
func TestBuilderPrepare_SSHTimeout(t *testing.T) {
var b Builder
config := testConfig()
// Test with a bad value
config["ssh_timeout"] = "this is not good"
err := b.Prepare(config)
if err == nil {
t.Fatal("should have error")
}
// Test with a good one
config["ssh_timeout"] = "5s"
err = b.Prepare(config)
if err != nil {
t.Fatalf("should not have error: %s", err)
}
}
func TestBuilderPrepare_SSHUsername(t *testing.T) { func TestBuilderPrepare_SSHUsername(t *testing.T) {
var b Builder var b Builder
config := testConfig() config := testConfig()
......
...@@ -39,17 +39,50 @@ func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction ...@@ -39,17 +39,50 @@ func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction
}, },
} }
// Try to connect for SSH a few times // Start trying to connect to SSH
ui.Say("Connecting to the instance via SSH...") connected := make(chan bool, 1)
for i := 0; i < 5; i++ { connectQuit := make(chan bool, 1)
time.Sleep(time.Duration(i) * time.Second) defer func() {
connectQuit <- true
log.Printf( }()
"Opening TCP conn for SSH to %s:%d (attempt %d)",
instance.DNSName, config.SSHPort, i+1) go func() {
s.conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort)) var err error
if err != nil {
continue ui.Say("Connecting to the instance via SSH...")
attempts := 0
for {
select {
case <-connectQuit:
return
default:
}
attempts += 1
log.Printf(
"Opening TCP conn for SSH to %s:%d (attempt %d)",
instance.DNSName, config.SSHPort, attempts)
s.conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort))
if err == nil {
break
}
}
connected <- true
}()
log.Printf("Waiting up to %s for SSH connection", config.SSHTimeout)
timeout := time.After(config.SSHTimeout)
ConnectWaitLoop:
for {
select {
case <-connected:
// We connected. Just break the loop.
break ConnectWaitLoop
case <-timeout:
ui.Error("Timeout while waiting to connect to SSH.")
return multistep.ActionHalt
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment