github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/retrystrategy/retrystrategy.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Copyright 2016 Cloudbase Solutions
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package retrystrategy
     6  
     7  import (
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names"
    12  
    13  	"github.com/juju/juju/apiserver/common"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/state"
    16  	"github.com/juju/juju/state/watcher"
    17  )
    18  
    19  // Right now, these are defined as constants, but the plan is to maybe make
    20  // them configurable in the future
    21  const (
    22  	MinRetryTime    = 5 * time.Second
    23  	MaxRetryTime    = 5 * time.Minute
    24  	JitterRetryTime = true
    25  	RetryTimeFactor = 2
    26  )
    27  
    28  func init() {
    29  	common.RegisterStandardFacade("RetryStrategy", 1, NewRetryStrategyAPI)
    30  }
    31  
    32  // RetryStrategy defines the methods exported by the RetryStrategy API facade.
    33  type RetryStrategy interface {
    34  	RetryStrategy(params.Entities) (params.RetryStrategyResults, error)
    35  	WatchRetryStrategy(params.Entities) (params.NotifyWatchResults, error)
    36  }
    37  
    38  // RetryStrategyAPI implements RetryStrategy
    39  type RetryStrategyAPI struct {
    40  	st         *state.State
    41  	accessUnit common.GetAuthFunc
    42  	resources  *common.Resources
    43  }
    44  
    45  var _ RetryStrategy = (*RetryStrategyAPI)(nil)
    46  
    47  // NewRetryStrategyAPI creates a new API endpoint for getting retry strategies.
    48  func NewRetryStrategyAPI(
    49  	st *state.State,
    50  	resources *common.Resources,
    51  	authorizer common.Authorizer,
    52  ) (*RetryStrategyAPI, error) {
    53  	if !authorizer.AuthUnitAgent() {
    54  		return nil, common.ErrPerm
    55  	}
    56  	return &RetryStrategyAPI{
    57  		st: st,
    58  		accessUnit: func() (common.AuthFunc, error) {
    59  			return authorizer.AuthOwner, nil
    60  		},
    61  		resources: resources,
    62  	}, nil
    63  }
    64  
    65  // RetryStrategy returns RetryStrategyResults that can be used by any code that uses
    66  // to configure the retry timer that's currently in juju utils.
    67  func (h *RetryStrategyAPI) RetryStrategy(args params.Entities) (params.RetryStrategyResults, error) {
    68  	results := params.RetryStrategyResults{
    69  		Results: make([]params.RetryStrategyResult, len(args.Entities)),
    70  	}
    71  	canAccess, err := h.accessUnit()
    72  	if err != nil {
    73  		return params.RetryStrategyResults{}, errors.Trace(err)
    74  	}
    75  	config, err := h.st.ModelConfig()
    76  	if err != nil {
    77  		return params.RetryStrategyResults{}, errors.Trace(err)
    78  	}
    79  	for i, entity := range args.Entities {
    80  		tag, err := names.ParseTag(entity.Tag)
    81  		if err != nil {
    82  			results.Results[i].Error = common.ServerError(err)
    83  			continue
    84  		}
    85  		err = common.ErrPerm
    86  		if canAccess(tag) {
    87  			// Right now the only real configurable value is ShouldRetry,
    88  			// which is taken from the environment
    89  			// The rest are hardcoded
    90  			results.Results[i].Result = &params.RetryStrategy{
    91  				ShouldRetry:     config.AutomaticallyRetryHooks(),
    92  				MinRetryTime:    MinRetryTime,
    93  				MaxRetryTime:    MaxRetryTime,
    94  				JitterRetryTime: JitterRetryTime,
    95  				RetryTimeFactor: RetryTimeFactor,
    96  			}
    97  			err = nil
    98  		}
    99  		results.Results[i].Error = common.ServerError(err)
   100  	}
   101  	return results, nil
   102  }
   103  
   104  // WatchRetryStrategy watches for changes to the environment. Currently we only allow
   105  // changes to the boolean that determines whether retries should be attempted or not.
   106  func (h *RetryStrategyAPI) WatchRetryStrategy(args params.Entities) (params.NotifyWatchResults, error) {
   107  	results := params.NotifyWatchResults{
   108  		Results: make([]params.NotifyWatchResult, len(args.Entities)),
   109  	}
   110  	canAccess, err := h.accessUnit()
   111  	if err != nil {
   112  		return params.NotifyWatchResults{}, errors.Trace(err)
   113  	}
   114  	for i, entity := range args.Entities {
   115  		tag, err := names.ParseTag(entity.Tag)
   116  		if err != nil {
   117  			results.Results[i].Error = common.ServerError(err)
   118  			continue
   119  		}
   120  		err = common.ErrPerm
   121  		if canAccess(tag) {
   122  			watch := h.st.WatchForModelConfigChanges()
   123  			// Consume the initial event. Technically, API calls to Watch
   124  			// 'transmit' the initial event in the Watch response. But
   125  			// NotifyWatchers have no state to transmit.
   126  			if _, ok := <-watch.Changes(); ok {
   127  				results.Results[i].NotifyWatcherId = h.resources.Register(watch)
   128  				err = nil
   129  			} else {
   130  				err = watcher.EnsureErr(watch)
   131  			}
   132  		}
   133  		results.Results[i].Error = common.ServerError(err)
   134  	}
   135  	return results, nil
   136  }