From a9ffa1559e67d3765afcb85cd41efac42dd4cf4c Mon Sep 17 00:00:00 2001 From: Vladislav Rassokhin Date: Fri, 12 Jul 2019 12:44:40 +0300 Subject: [PATCH] Don't wait for IP till heat death of the universe Introduced 'ip_wait_timeout' parameter with '5m' (five minutes) default value. --- README.md | 1 + common/step_wait_for_ip.go | 44 +++++++++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c1c542df3..d5e3103fa 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ See complete Ubuntu, Windows, and macOS templates in the [examples folder](https ### Provision * `communicator` - `ssh` (default), `winrm`, or `none` (create/clone, customize hardware, but do not boot). +* `ip_wait_timeout`(string) - Amount of time to wait for VM's IP, similar to 'ssh_timeout'. Defaults to 5m (5 minutes). See the Go Lang [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details. * `ip_settle_timeout`(string) - Amount of time to wait for VM's IP to settle down, sometimes VM may report incorrect IP initially, then its recommended to set that parameter to apx. 2 minutes. Examples 45s and 10m. Defaults to 5s(5 seconds). See the Go Lang [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details. * `ssh_username`(string) - Username in guest OS. * `ssh_password`(string) - Password to access guest OS. Only specify `ssh_password` or `ssh_private_key_file`, but not both. diff --git a/common/step_wait_for_ip.go b/common/step_wait_for_ip.go index 64a841538..f70ee26d8 100644 --- a/common/step_wait_for_ip.go +++ b/common/step_wait_for_ip.go @@ -11,7 +11,10 @@ import ( ) type WaitIpConfig struct { + WaitTimeout time.Duration `mapstructure:"ip_wait_timeout"` SettleTimeout time.Duration `mapstructure:"ip_settle_timeout"` + + // WaitTimeout is a total timeout, so even if VM changes IP frequently and it doesn't settle down we will end waiting. } type StepWaitForIp struct { @@ -24,6 +27,10 @@ func (c *WaitIpConfig) Prepare() []error { if c.SettleTimeout == 0 { c.SettleTimeout = 5 * time.Second } + if c.WaitTimeout == 0 { + // Same default value as default timeout for 'ssh_timeout' in StepConnect + c.SettleTimeout = 5 * time.Minute + } return errs } @@ -32,22 +39,37 @@ func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multi ui := state.Get("ui").(packer.Ui) vm := state.Get("vm").(*driver.VirtualMachine) - ui.Say("Waiting for IP...") + var ip string + var err error + + sub, cancel := context.WithCancel(ctx) + waitDone := make(chan bool, 1) - ipChan := make(chan string) - errChan := make(chan error) go func() { - doGetIp(vm, ctx, s.Config, errChan, ipChan) + ui.Say("Waiting for IP...") + ip, err = doGetIp(vm, sub, s.Config) + waitDone <- true }() + log.Printf("[INFO] Waiting for IP, up to total timeout: %s, settle timeout: %s", s.Config.WaitTimeout, s.Config.SettleTimeout) + timeout := time.After(s.Config.WaitTimeout) for { select { - case err := <-errChan: + case <-timeout: + err := fmt.Errorf("Timeout waiting for IP.") state.Put("error", err) + ui.Error(err.Error()) + cancel() return multistep.ActionHalt case <-ctx.Done(): + cancel() + log.Println("[WARN] Interrupt detected, quitting waiting for IP.") return multistep.ActionHalt - case ip := <-ipChan: + case <-waitDone: + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } state.Put("ip", ip) ui.Say(fmt.Sprintf("IP address: %v", ip)) return multistep.ActionContinue @@ -59,7 +81,7 @@ func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multi } } -func doGetIp(vm *driver.VirtualMachine, ctx context.Context, c *WaitIpConfig, errChan chan error, ipChan chan string) { +func doGetIp(vm *driver.VirtualMachine, ctx context.Context, c *WaitIpConfig) (string, error) { var prevIp = "" var stopTime time.Time var interval time.Duration @@ -75,8 +97,7 @@ func doGetIp(vm *driver.VirtualMachine, ctx context.Context, c *WaitIpConfig, er loop: ip, err := vm.WaitForIP(ctx) if err != nil { - errChan <- err - return + return "", err } if prevIp == "" || prevIp != ip { if prevIp == "" { @@ -91,12 +112,11 @@ loop: log.Printf("VM IP is still the same: %s", prevIp) if time.Now().After(stopTime) { log.Printf("VM IP seems stable enough: %s", ip) - ipChan <- ip - return + return ip, nil } select { case <-ctx.Done(): - return + return "", fmt.Errorf("IP wait cancelled") case <-time.After(interval): goto loop }