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 (
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
// The unique ID for this builder
......@@ -30,9 +31,12 @@ type config struct {
InstanceType string `mapstructure:"instance_type"`
SSHUsername string `mapstructure:"ssh_username"`
SSHPort int `mapstructure:"ssh_port"`
SSHTimeout time.Duration
// Configuration of the resulting AMI
AMIName string `mapstructure:"ami_name"`
RawSSHTimeout string `mapstructure:"ssh_timeout"`
}
type Builder struct {
......@@ -40,16 +44,20 @@ type Builder struct {
runner multistep.Runner
}
func (b *Builder) Prepare(raw interface{}) (err error) {
err = mapstructure.Decode(raw, &b.config)
func (b *Builder) Prepare(raw interface{}) error {
err := mapstructure.Decode(raw, &b.config)
if err != nil {
return
return err
}
if b.config.SSHPort == 0 {
b.config.SSHPort = 22
}
if b.config.RawSSHTimeout == "" {
b.config.RawSSHTimeout = "1m"
}
// Accumulate any errors
errs := make([]error, 0)
......@@ -79,12 +87,17 @@ func (b *Builder) Prepare(raw interface{}) (err error) {
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 {
return &packer.MultiError{errs}
}
log.Printf("Config: %+v", b.config)
return
return nil
}
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) {
}
}
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) {
var b Builder
config := testConfig()
......
......@@ -39,17 +39,50 @@ func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction
},
}
// Try to connect for SSH a few times
ui.Say("Connecting to the instance via SSH...")
for i := 0; i < 5; i++ {
time.Sleep(time.Duration(i) * time.Second)
log.Printf(
"Opening TCP conn for SSH to %s:%d (attempt %d)",
instance.DNSName, config.SSHPort, i+1)
s.conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", instance.DNSName, config.SSHPort))
if err != nil {
continue
// Start trying to connect to SSH
connected := make(chan bool, 1)
connectQuit := make(chan bool, 1)
defer func() {
connectQuit <- true
}()
go func() {
var err error
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