github.com/grange74/terraform@v0.7.0-rc3.0.20160722171430-8c8803864753/helper/resource/state.go (about)

     1  package resource
     2  
     3  import (
     4  	"log"
     5  	"math"
     6  	"time"
     7  )
     8  
     9  // StateRefreshFunc is a function type used for StateChangeConf that is
    10  // responsible for refreshing the item being watched for a state change.
    11  //
    12  // It returns three results. `result` is any object that will be returned
    13  // as the final object after waiting for state change. This allows you to
    14  // return the final updated object, for example an EC2 instance after refreshing
    15  // it.
    16  //
    17  // `state` is the latest state of that object. And `err` is any error that
    18  // may have happened while refreshing the state.
    19  type StateRefreshFunc func() (result interface{}, state string, err error)
    20  
    21  // StateChangeConf is the configuration struct used for `WaitForState`.
    22  type StateChangeConf struct {
    23  	Delay          time.Duration    // Wait this time before starting checks
    24  	Pending        []string         // States that are "allowed" and will continue trying
    25  	Refresh        StateRefreshFunc // Refreshes the current state
    26  	Target         []string         // Target state
    27  	Timeout        time.Duration    // The amount of time to wait before timeout
    28  	MinTimeout     time.Duration    // Smallest time to wait before refreshes
    29  	PollInterval   time.Duration    // Override MinTimeout/backoff and only poll this often
    30  	NotFoundChecks int              // Number of times to allow not found
    31  
    32  	// This is to work around inconsistent APIs
    33  	ContinuousTargetOccurence int // Number of times the Target state has to occur continuously
    34  }
    35  
    36  // WaitForState watches an object and waits for it to achieve the state
    37  // specified in the configuration using the specified Refresh() func,
    38  // waiting the number of seconds specified in the timeout configuration.
    39  //
    40  // If the Refresh function returns a error, exit immediately with that error.
    41  //
    42  // If the Refresh function returns a state other than the Target state or one
    43  // listed in Pending, return immediately with an error.
    44  //
    45  // If the Timeout is exceeded before reaching the Target state, return an
    46  // error.
    47  //
    48  // Otherwise, result the result of the first call to the Refresh function to
    49  // reach the target state.
    50  func (conf *StateChangeConf) WaitForState() (interface{}, error) {
    51  	log.Printf("[DEBUG] Waiting for state to become: %s", conf.Target)
    52  
    53  	notfoundTick := 0
    54  	targetOccurence := 0
    55  
    56  	// Set a default for times to check for not found
    57  	if conf.NotFoundChecks == 0 {
    58  		conf.NotFoundChecks = 20
    59  	}
    60  
    61  	if conf.ContinuousTargetOccurence == 0 {
    62  		conf.ContinuousTargetOccurence = 1
    63  	}
    64  
    65  	var result interface{}
    66  	var resulterr error
    67  
    68  	doneCh := make(chan struct{})
    69  	go func() {
    70  		defer close(doneCh)
    71  
    72  		// Wait for the delay
    73  		time.Sleep(conf.Delay)
    74  
    75  		var err error
    76  		var wait time.Duration
    77  		for tries := 0; ; tries++ {
    78  			// Wait between refreshes using an exponential backoff
    79  			// If a poll interval has been specified, choose that interval
    80  			if conf.PollInterval > 0 && conf.PollInterval < 180*time.Second {
    81  				wait = conf.PollInterval
    82  			} else {
    83  				wait = time.Duration(math.Pow(2, float64(tries))) *
    84  					100 * time.Millisecond
    85  				if wait < conf.MinTimeout {
    86  					wait = conf.MinTimeout
    87  				} else if wait > 10*time.Second {
    88  					wait = 10 * time.Second
    89  				}
    90  			}
    91  
    92  			log.Printf("[TRACE] Waiting %s before next try", wait)
    93  			time.Sleep(wait)
    94  
    95  			var currentState string
    96  			result, currentState, err = conf.Refresh()
    97  			if err != nil {
    98  				resulterr = err
    99  				return
   100  			}
   101  
   102  			// If we're waiting for the absence of a thing, then return
   103  			if result == nil && len(conf.Target) == 0 {
   104  				targetOccurence += 1
   105  				if conf.ContinuousTargetOccurence == targetOccurence {
   106  					return
   107  				} else {
   108  					continue
   109  				}
   110  			}
   111  
   112  			if result == nil {
   113  				// If we didn't find the resource, check if we have been
   114  				// not finding it for awhile, and if so, report an error.
   115  				notfoundTick += 1
   116  				if notfoundTick > conf.NotFoundChecks {
   117  					resulterr = &NotFoundError{
   118  						LastError: resulterr,
   119  					}
   120  					return
   121  				}
   122  			} else {
   123  				// Reset the counter for when a resource isn't found
   124  				notfoundTick = 0
   125  				found := false
   126  
   127  				for _, allowed := range conf.Target {
   128  					if currentState == allowed {
   129  						found = true
   130  						targetOccurence += 1
   131  						if conf.ContinuousTargetOccurence == targetOccurence {
   132  							return
   133  						} else {
   134  							continue
   135  						}
   136  					}
   137  				}
   138  
   139  				for _, allowed := range conf.Pending {
   140  					if currentState == allowed {
   141  						found = true
   142  						targetOccurence = 0
   143  						break
   144  					}
   145  				}
   146  
   147  				if !found {
   148  					resulterr = &UnexpectedStateError{
   149  						LastError:     resulterr,
   150  						State:         currentState,
   151  						ExpectedState: conf.Target,
   152  					}
   153  					return
   154  				}
   155  			}
   156  		}
   157  	}()
   158  
   159  	select {
   160  	case <-doneCh:
   161  		return result, resulterr
   162  	case <-time.After(conf.Timeout):
   163  		return nil, &TimeoutError{
   164  			LastError:     resulterr,
   165  			ExpectedState: conf.Target,
   166  		}
   167  	}
   168  }