github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/helper/resource/state.go (about)

     1  package resource
     2  
     3  import (
     4  	"log"
     5  	"sync/atomic"
     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  	// We can't safely read the result values if we timeout, so store them in
    66  	// an atomic.Value
    67  	type Result struct {
    68  		Result interface{}
    69  		State  string
    70  		Error  error
    71  	}
    72  	var lastResult atomic.Value
    73  	lastResult.Store(Result{})
    74  
    75  	doneCh := make(chan struct{})
    76  	go func() {
    77  		defer close(doneCh)
    78  
    79  		// Wait for the delay
    80  		time.Sleep(conf.Delay)
    81  
    82  		wait := 100 * time.Millisecond
    83  
    84  		for {
    85  			res, currentState, err := conf.Refresh()
    86  			result := Result{
    87  				Result: res,
    88  				State:  currentState,
    89  				Error:  err,
    90  			}
    91  			lastResult.Store(result)
    92  
    93  			if err != nil {
    94  				return
    95  			}
    96  
    97  			// If we're waiting for the absence of a thing, then return
    98  			if res == nil && len(conf.Target) == 0 {
    99  				targetOccurence += 1
   100  				if conf.ContinuousTargetOccurence == targetOccurence {
   101  					return
   102  				} else {
   103  					continue
   104  				}
   105  			}
   106  
   107  			if res == nil {
   108  				// If we didn't find the resource, check if we have been
   109  				// not finding it for awhile, and if so, report an error.
   110  				notfoundTick += 1
   111  				if notfoundTick > conf.NotFoundChecks {
   112  					result.Error = &NotFoundError{
   113  						LastError: err,
   114  						Retries:   notfoundTick,
   115  					}
   116  					lastResult.Store(result)
   117  					return
   118  				}
   119  			} else {
   120  				// Reset the counter for when a resource isn't found
   121  				notfoundTick = 0
   122  				found := false
   123  
   124  				for _, allowed := range conf.Target {
   125  					if currentState == allowed {
   126  						found = true
   127  						targetOccurence += 1
   128  						if conf.ContinuousTargetOccurence == targetOccurence {
   129  							return
   130  						} else {
   131  							continue
   132  						}
   133  					}
   134  				}
   135  
   136  				for _, allowed := range conf.Pending {
   137  					if currentState == allowed {
   138  						found = true
   139  						targetOccurence = 0
   140  						break
   141  					}
   142  				}
   143  
   144  				if !found {
   145  					result.Error = &UnexpectedStateError{
   146  						LastError:     err,
   147  						State:         result.State,
   148  						ExpectedState: conf.Target,
   149  					}
   150  					lastResult.Store(result)
   151  					return
   152  				}
   153  			}
   154  
   155  			// If a poll interval has been specified, choose that interval.
   156  			// Otherwise bound the default value.
   157  			if conf.PollInterval > 0 && conf.PollInterval < 180*time.Second {
   158  				wait = conf.PollInterval
   159  			} else {
   160  				if wait < conf.MinTimeout {
   161  					wait = conf.MinTimeout
   162  				} else if wait > 10*time.Second {
   163  					wait = 10 * time.Second
   164  				}
   165  			}
   166  
   167  			log.Printf("[TRACE] Waiting %s before next try", wait)
   168  			time.Sleep(wait)
   169  
   170  			// Wait between refreshes using exponential backoff, except when
   171  			// waiting for the target state to reoccur.
   172  			if targetOccurence == 0 {
   173  				wait *= 2
   174  			}
   175  		}
   176  	}()
   177  
   178  	select {
   179  	case <-doneCh:
   180  		r := lastResult.Load().(Result)
   181  		return r.Result, r.Error
   182  	case <-time.After(conf.Timeout):
   183  		r := lastResult.Load().(Result)
   184  		return nil, &TimeoutError{
   185  			LastError:     r.Error,
   186  			LastState:     r.State,
   187  			Timeout:       conf.Timeout,
   188  			ExpectedState: conf.Target,
   189  		}
   190  	}
   191  }