github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/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  					}
   115  					lastResult.Store(result)
   116  					return
   117  				}
   118  			} else {
   119  				// Reset the counter for when a resource isn't found
   120  				notfoundTick = 0
   121  				found := false
   122  
   123  				for _, allowed := range conf.Target {
   124  					if currentState == allowed {
   125  						found = true
   126  						targetOccurence += 1
   127  						if conf.ContinuousTargetOccurence == targetOccurence {
   128  							return
   129  						} else {
   130  							continue
   131  						}
   132  					}
   133  				}
   134  
   135  				for _, allowed := range conf.Pending {
   136  					if currentState == allowed {
   137  						found = true
   138  						targetOccurence = 0
   139  						break
   140  					}
   141  				}
   142  
   143  				if !found {
   144  					result.Error = &UnexpectedStateError{
   145  						LastError:     err,
   146  						State:         result.State,
   147  						ExpectedState: conf.Target,
   148  					}
   149  					lastResult.Store(result)
   150  					return
   151  				}
   152  			}
   153  
   154  			// If a poll interval has been specified, choose that interval.
   155  			// Otherwise bound the default value.
   156  			if conf.PollInterval > 0 && conf.PollInterval < 180*time.Second {
   157  				wait = conf.PollInterval
   158  			} else {
   159  				if wait < conf.MinTimeout {
   160  					wait = conf.MinTimeout
   161  				} else if wait > 10*time.Second {
   162  					wait = 10 * time.Second
   163  				}
   164  			}
   165  
   166  			log.Printf("[TRACE] Waiting %s before next try", wait)
   167  			time.Sleep(wait)
   168  
   169  			// Wait between refreshes using exponential backoff, except when
   170  			// waiting for the target state to reoccur.
   171  			if targetOccurence == 0 {
   172  				wait *= 2
   173  			}
   174  		}
   175  	}()
   176  
   177  	select {
   178  	case <-doneCh:
   179  		r := lastResult.Load().(Result)
   180  		return r.Result, r.Error
   181  	case <-time.After(conf.Timeout):
   182  		r := lastResult.Load().(Result)
   183  		return nil, &TimeoutError{
   184  			LastError:     r.Error,
   185  			LastState:     r.State,
   186  			ExpectedState: conf.Target,
   187  		}
   188  	}
   189  }