github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/featureflag/flag.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package featureflag
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"gopkg.in/juju/worker.v1"
    10  	"gopkg.in/juju/worker.v1/catacomb"
    11  
    12  	"github.com/juju/juju/controller"
    13  	"github.com/juju/juju/state"
    14  )
    15  
    16  // This implements a flag worker that restarts any time the specified
    17  // feature flag is turned on or off, and exposes whether the the
    18  // feature flag is set in its Check method. (The sense of the feature
    19  // flag can be inverted if needed, so that Check returns true when it
    20  // is off.)
    21  
    22  // ConfigSource lets us get notifications of changes to controller
    23  // configuration, and then get the changed config. (Primary
    24  // implementation is State.)
    25  type ConfigSource interface {
    26  	WatchControllerConfig() state.NotifyWatcher
    27  	ControllerConfig() (controller.Config, error)
    28  }
    29  
    30  // ErrRefresh indicates that the flag's Check result is no longer valid,
    31  // and a new featureflag.Worker must be started to get a valid result.
    32  var ErrRefresh = errors.New("feature flag changed, restart worker")
    33  
    34  // Config holds the information needed by the featureflag worker.
    35  type Config struct {
    36  	Source   ConfigSource
    37  	Logger   loggo.Logger
    38  	FlagName string
    39  	Invert   bool
    40  }
    41  
    42  // Value returns whether the feature flag is set (inverted if
    43  // necessary).
    44  func (config Config) Value() (bool, error) {
    45  	controllerConfig, err := config.Source.ControllerConfig()
    46  	if err != nil {
    47  		return false, errors.Annotate(err, "getting controller config")
    48  	}
    49  	flagSet := controllerConfig.Features().Contains(config.FlagName)
    50  	return flagSet != config.Invert, nil
    51  }
    52  
    53  // Worker implements worker.Worker and util.Flag, representing
    54  // controller ownership of a model, such that the Flag's validity is
    55  // tied to the Worker's lifetime.
    56  type Worker struct {
    57  	catacomb catacomb.Catacomb
    58  	config   Config
    59  	value    bool
    60  }
    61  
    62  // NewWorker creates a feature flag worker with the specified config.
    63  func NewWorker(config Config) (worker.Worker, error) {
    64  	value, err := config.Value()
    65  	if err != nil {
    66  		return nil, errors.Trace(err)
    67  	}
    68  	flag := &Worker{
    69  		config: config,
    70  		value:  value,
    71  	}
    72  	if err := catacomb.Invoke(catacomb.Plan{
    73  		Site: &flag.catacomb,
    74  		Work: flag.loop,
    75  	}); err != nil {
    76  		return nil, errors.Trace(err)
    77  	}
    78  	return flag, nil
    79  }
    80  
    81  // Kill is part of the worker.Worker interface.
    82  func (flag *Worker) Kill() {
    83  	flag.catacomb.Kill(nil)
    84  }
    85  
    86  // Wait is part of the worker.Worker interface.
    87  func (flag *Worker) Wait() error {
    88  	return flag.catacomb.Wait()
    89  }
    90  
    91  // Check is part of the util.Flag interface.
    92  //
    93  // Check returns whether the feature flag is set (or the not set, if
    94  // Invert was set).
    95  //
    96  // The validity of this result is tied to the lifetime of the Worker;
    97  // once the worker has stopped, no inferences may be drawn from any
    98  // Check result.
    99  func (flag *Worker) Check() bool {
   100  	return flag.value
   101  }
   102  
   103  func (flag *Worker) loop() error {
   104  	watcher := flag.config.Source.WatchControllerConfig()
   105  	if err := flag.catacomb.Add(watcher); err != nil {
   106  		return errors.Trace(err)
   107  	}
   108  
   109  	for {
   110  		select {
   111  		case <-flag.catacomb.Dying():
   112  			return flag.catacomb.ErrDying()
   113  		case _, ok := <-watcher.Changes():
   114  			if !ok {
   115  				return errors.Errorf("watcher channel closed")
   116  			}
   117  			newValue, err := flag.config.Value()
   118  			if err != nil {
   119  				return errors.Trace(err)
   120  			}
   121  			if newValue != flag.value {
   122  				flag.config.Logger.Debugf("feature flag changed: %v", newValue)
   123  				return ErrRefresh
   124  			}
   125  		}
   126  	}
   127  }