Use context for timeouts, interruption in ssh and winrm communicators

Also don't waste 5 seconds waiting before first winrm connection attempt
Minor code cleanup as well
This commit is contained in:
Vladislav Rassokhin 2019-07-12 12:54:43 +03:00
parent e229f75353
commit 19e8f150a3
2 changed files with 37 additions and 39 deletions

View File

@ -36,17 +36,17 @@ func (s *StepConnectSSH) Run(ctx context.Context, state multistep.StateBag) mult
var comm packer.Communicator var comm packer.Communicator
var err error var err error
cancel := make(chan struct{}) subCtx, cancel := context.WithCancel(ctx)
waitDone := make(chan bool, 1) waitDone := make(chan bool, 1)
go func() { go func() {
ui.Say("Waiting for SSH to become available...") ui.Say("Waiting for SSH to become available...")
comm, err = s.waitForSSH(state, cancel) comm, err = s.waitForSSH(state, subCtx)
cancel() // just to make 'possible context leak' analysis happy
waitDone <- true waitDone <- true
}() }()
log.Printf("[INFO] Waiting for SSH, up to timeout: %s", s.Config.SSHTimeout) log.Printf("[INFO] Waiting for SSH, up to timeout: %s", s.Config.SSHTimeout)
timeout := time.After(s.Config.SSHTimeout) timeout := time.After(s.Config.SSHTimeout)
WaitLoop:
for { for {
// Wait for either SSH to become available, a timeout to occur, // Wait for either SSH to become available, a timeout to occur,
// or an interrupt to come through. // or an interrupt to come through.
@ -60,31 +60,28 @@ WaitLoop:
ui.Say("Connected to SSH!") ui.Say("Connected to SSH!")
state.Put("communicator", comm) state.Put("communicator", comm)
break WaitLoop return multistep.ActionContinue
case <-timeout: case <-timeout:
err := fmt.Errorf("Timeout waiting for SSH.") err := fmt.Errorf("Timeout waiting for SSH.")
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
close(cancel) cancel()
return multistep.ActionHalt
case <-ctx.Done():
// The step sequence was cancelled, so cancel waiting for SSH
// and just start the halting process.
cancel()
log.Println("[WARN] Interrupt detected, quitting waiting for SSH.")
return multistep.ActionHalt return multistep.ActionHalt
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
// The step sequence was cancelled, so cancel waiting for SSH
// and just start the halting process.
close(cancel)
log.Println("[WARN] Interrupt detected, quitting waiting for SSH.")
return multistep.ActionHalt
}
} }
} }
return multistep.ActionContinue
} }
func (s *StepConnectSSH) Cleanup(multistep.StateBag) { func (s *StepConnectSSH) Cleanup(multistep.StateBag) {
} }
func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) { func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, ctx context.Context) (packer.Communicator, error) {
// Determine if we're using a bastion host, and if so, retrieve // Determine if we're using a bastion host, and if so, retrieve
// that configuration. This configuration doesn't change so we // that configuration. This configuration doesn't change so we
// do this one before entering the retry loop. // do this one before entering the retry loop.
@ -123,7 +120,7 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
// Don't check for cancel or wait on first iteration // Don't check for cancel or wait on first iteration
if !first { if !first {
select { select {
case <-cancel: case <-ctx.Done():
log.Println("[DEBUG] SSH wait cancelled. Exiting loop.") log.Println("[DEBUG] SSH wait cancelled. Exiting loop.")
return nil, errors.New("SSH wait cancelled") return nil, errors.New("SSH wait cancelled")
case <-time.After(5 * time.Second): case <-time.After(5 * time.Second):

View File

@ -33,23 +33,23 @@ type StepConnectWinRM struct {
WinRMPort func(multistep.StateBag) (int, error) WinRMPort func(multistep.StateBag) (int, error)
} }
func (s *StepConnectWinRM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepConnectWinRM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
var comm packer.Communicator var comm packer.Communicator
var err error var err error
cancel := make(chan struct{}) subCtx, cancel := context.WithCancel(ctx)
waitDone := make(chan bool, 1) waitDone := make(chan bool, 1)
go func() { go func() {
ui.Say("Waiting for WinRM to become available...") ui.Say("Waiting for WinRM to become available...")
comm, err = s.waitForWinRM(state, cancel) comm, err = s.waitForWinRM(state, subCtx)
cancel() // just to make 'possible context leak' analysis happy
waitDone <- true waitDone <- true
}() }()
log.Printf("Waiting for WinRM, up to timeout: %s", s.Config.WinRMTimeout) log.Printf("Waiting for WinRM, up to timeout: %s", s.Config.WinRMTimeout)
timeout := time.After(s.Config.WinRMTimeout) timeout := time.After(s.Config.WinRMTimeout)
WaitLoop:
for { for {
// Wait for either WinRM to become available, a timeout to occur, // Wait for either WinRM to become available, a timeout to occur,
// or an interrupt to come through. // or an interrupt to come through.
@ -62,39 +62,40 @@ WaitLoop:
ui.Say("Connected to WinRM!") ui.Say("Connected to WinRM!")
state.Put("communicator", comm) state.Put("communicator", comm)
break WaitLoop return multistep.ActionContinue
case <-timeout: case <-timeout:
err := fmt.Errorf("Timeout waiting for WinRM.") err := fmt.Errorf("Timeout waiting for WinRM.")
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
close(cancel) cancel()
return multistep.ActionHalt
case <-ctx.Done():
// The step sequence was cancelled, so cancel waiting for WinRM
// and just start the halting process.
cancel()
log.Println("Interrupt detected, quitting waiting for WinRM.")
return multistep.ActionHalt return multistep.ActionHalt
case <-time.After(1 * time.Second): case <-time.After(1 * time.Second):
if _, ok := state.GetOk(multistep.StateCancelled); ok {
// The step sequence was cancelled, so cancel waiting for WinRM
// and just start the halting process.
close(cancel)
log.Println("Interrupt detected, quitting waiting for WinRM.")
return multistep.ActionHalt
}
} }
} }
return multistep.ActionContinue
} }
func (s *StepConnectWinRM) Cleanup(multistep.StateBag) { func (s *StepConnectWinRM) Cleanup(multistep.StateBag) {
} }
func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) { func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, ctx context.Context) (packer.Communicator, error) {
ctx := context.TODO()
var comm packer.Communicator var comm packer.Communicator
first := true
for { for {
select { // Don't check for cancel or wait on first iteration
case <-cancel: if !first {
log.Println("[INFO] WinRM wait cancelled. Exiting loop.") select {
return nil, errors.New("WinRM wait cancelled") case <-ctx.Done():
case <-time.After(5 * time.Second): log.Println("[INFO] WinRM wait cancelled. Exiting loop.")
return nil, errors.New("WinRM wait cancelled")
case <-time.After(5 * time.Second):
}
first = false
} }
host, err := s.Host(state) host, err := s.Host(state)
@ -157,7 +158,7 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan
cmd.Stdout = &buf cmd.Stdout = &buf
cmd.Stdout = io.MultiWriter(cmd.Stdout, &buf2) cmd.Stdout = io.MultiWriter(cmd.Stdout, &buf2)
select { select {
case <-cancel: case <-ctx.Done():
log.Println("WinRM wait canceled, exiting loop") log.Println("WinRM wait canceled, exiting loop")
return comm, fmt.Errorf("WinRM wait canceled") return comm, fmt.Errorf("WinRM wait canceled")
case <-time.After(retryableSleep): case <-time.After(retryableSleep):