github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/worker/singular/flag.go (about)

     1  // Copyright 2015-2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package singular
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/juju/core/lease"
    11  	"github.com/juju/juju/worker/catacomb"
    12  	"github.com/juju/utils/clock"
    13  )
    14  
    15  // Facade exposes the capabilities required by a FlagWorker.
    16  type Facade interface {
    17  	Claim(duration time.Duration) error
    18  	Wait() error
    19  }
    20  
    21  // FlagConfig holds a FlagWorker's dependencies and resources.
    22  type FlagConfig struct {
    23  	Clock    clock.Clock
    24  	Facade   Facade
    25  	Duration time.Duration
    26  }
    27  
    28  // Validate returns an error if the config cannot be expected to run a
    29  // FlagWorker.
    30  func (config FlagConfig) Validate() error {
    31  	if config.Clock == nil {
    32  		return errors.NotValidf("nil Clock")
    33  	}
    34  	if config.Facade == nil {
    35  		return errors.NotValidf("nil Facade")
    36  	}
    37  	if config.Duration <= 0 {
    38  		return errors.NotValidf("non-positive Duration")
    39  	}
    40  	return nil
    41  }
    42  
    43  // ErrRefresh indicates that the flag's Check result is no longer valid,
    44  // and a new FlagWorker must be started to get a valid result.
    45  var ErrRefresh = errors.New("model responsibility unclear, please retry")
    46  
    47  // FlagWorker implements worker.Worker and util.Flag, representing
    48  // controller ownership of a model, such that the Flag's validity is tied
    49  // to the Worker's lifetime.
    50  type FlagWorker struct {
    51  	catacomb catacomb.Catacomb
    52  	config   FlagConfig
    53  	valid    bool
    54  }
    55  
    56  func NewFlagWorker(config FlagConfig) (*FlagWorker, error) {
    57  	if err := config.Validate(); err != nil {
    58  		return nil, errors.Trace(err)
    59  	}
    60  	valid, err := claim(config)
    61  	if err != nil {
    62  		return nil, errors.Trace(err)
    63  	}
    64  	flag := &FlagWorker{
    65  		config: config,
    66  		valid:  valid,
    67  	}
    68  	err = catacomb.Invoke(catacomb.Plan{
    69  		Site: &flag.catacomb,
    70  		Work: flag.run,
    71  	})
    72  	if err != nil {
    73  		return nil, errors.Trace(err)
    74  	}
    75  	return flag, nil
    76  }
    77  
    78  // Kill is part of the worker.Worker interface.
    79  func (flag *FlagWorker) Kill() {
    80  	flag.catacomb.Kill(nil)
    81  }
    82  
    83  // Wait is part of the worker.Worker interface.
    84  func (flag *FlagWorker) Wait() error {
    85  	return flag.catacomb.Wait()
    86  }
    87  
    88  // Check is part of the util.Flag interface.
    89  //
    90  // Check returns true if the flag indicates that the configured Identity
    91  // (i.e. this controller) has taken control of the configured Scope (i.e.
    92  // the model we want to manage exclusively).
    93  //
    94  // The validity of this result is tied to the lifetime of the FlagWorker;
    95  // once the worker has stopped, no inferences may be drawn from any Check
    96  // result.
    97  func (flag *FlagWorker) Check() bool {
    98  	return flag.valid
    99  }
   100  
   101  // run invokes a suitable runFunc, depending on the value of .valid.
   102  func (flag *FlagWorker) run() error {
   103  	runFunc := waitVacant
   104  	if flag.valid {
   105  		runFunc = keepOccupied
   106  	}
   107  	err := runFunc(flag.config, flag.catacomb.Dying())
   108  	return errors.Trace(err)
   109  }
   110  
   111  // keepOccupied is a runFunc that tries to keep a flag valid.
   112  func keepOccupied(config FlagConfig, abort <-chan struct{}) error {
   113  	for {
   114  		select {
   115  		case <-abort:
   116  			return nil
   117  		case <-sleep(config):
   118  			success, err := claim(config)
   119  			if err != nil {
   120  				return errors.Trace(err)
   121  			}
   122  			if !success {
   123  				return ErrRefresh
   124  			}
   125  		}
   126  	}
   127  }
   128  
   129  // claim claims model ownership on behalf of a controller, and returns
   130  // true if the attempt succeeded.
   131  func claim(config FlagConfig) (bool, error) {
   132  	err := config.Facade.Claim(config.Duration)
   133  	cause := errors.Cause(err)
   134  	switch cause {
   135  	case nil:
   136  		return true, nil
   137  	case lease.ErrClaimDenied:
   138  		return false, nil
   139  	}
   140  	return false, errors.Trace(err)
   141  }
   142  
   143  // sleep waits for half the duration of a (presumed) earlier successful claim.
   144  func sleep(config FlagConfig) <-chan time.Time {
   145  	return config.Clock.After(config.Duration / 2)
   146  }
   147  
   148  // wait is a runFunc that ignores its abort chan and always returns an error;
   149  // either because of a failed api call, or a successful one, which indicates
   150  // that no lease is held; hence, that the worker should be bounced.
   151  func waitVacant(config FlagConfig, _ <-chan struct{}) error {
   152  	if err := config.Facade.Wait(); err != nil {
   153  		return errors.Trace(err)
   154  	}
   155  	return ErrRefresh
   156  }