github.com/daveadams/terraform@v0.6.4-0.20160830094355-13ce74975936/helper/resource/state.go (about)

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