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