github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/lifeflag/worker.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lifeflag
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/names/v5"
     9  	"github.com/juju/worker/v3/catacomb"
    10  
    11  	apilifeflag "github.com/juju/juju/api/common/lifeflag"
    12  	"github.com/juju/juju/core/life"
    13  	"github.com/juju/juju/core/watcher"
    14  )
    15  
    16  // Facade exposes capabilities required by the worker.
    17  type Facade interface {
    18  	Watch(names.Tag) (watcher.NotifyWatcher, error)
    19  	Life(names.Tag) (life.Value, error)
    20  }
    21  
    22  // Config holds the configuration and dependencies for a worker.
    23  type Config struct {
    24  	Facade         Facade
    25  	Entity         names.Tag
    26  	Result         life.Predicate
    27  	NotFoundIsDead bool
    28  }
    29  
    30  // Validate returns an error if the config cannot be expected
    31  // to drive a functional worker.
    32  func (config Config) Validate() error {
    33  	if config.Facade == nil {
    34  		return errors.NotValidf("nil Facade")
    35  	}
    36  	if config.Entity == nil {
    37  		return errors.NotValidf("nil Entity")
    38  	}
    39  	if config.Result == nil {
    40  		return errors.NotValidf("nil Result")
    41  	}
    42  	return nil
    43  }
    44  
    45  const (
    46  	// ErrNotFound indicates that the worker cannot run because
    47  	// the configured entity does not exist.
    48  	ErrNotFound = apilifeflag.ErrEntityNotFound
    49  
    50  	// ErrValueChanged indicates that the result of Check is
    51  	// outdated, and the worker should be restarted.
    52  	ErrValueChanged = errors.ConstError("flag value changed")
    53  )
    54  
    55  // New returns a worker that exposes the result of the configured
    56  // predicate when applied to the configured entity's life value,
    57  // and fails with ErrValueChanged when the result changes.
    58  func New(config Config) (*Worker, error) {
    59  	if err := config.Validate(); err != nil {
    60  		return nil, errors.Trace(err)
    61  	}
    62  
    63  	w := &Worker{
    64  		config: config,
    65  	}
    66  	plan := catacomb.Plan{
    67  		Site: &w.catacomb,
    68  		Work: w.loop,
    69  	}
    70  
    71  	var err error
    72  	// Read it before the worker starts, so that we have a value
    73  	// guaranteed before we return the worker. Because we read this
    74  	// before we start the internal watcher, we'll need an additional
    75  	// read triggered by the first change event; this will *probably*
    76  	// be the same value, but we can't assume it.
    77  	w.life, err = config.Facade.Life(config.Entity)
    78  	if config.NotFoundIsDead && errors.Is(err, ErrNotFound) {
    79  		// If we handle notfound as dead, we will always be dead.
    80  		w.life = life.Dead
    81  		plan.Work = w.alwaysDead
    82  	} else if err != nil {
    83  		return nil, errors.Trace(err)
    84  	}
    85  
    86  	err = catacomb.Invoke(plan)
    87  	if err != nil {
    88  		return nil, errors.Trace(err)
    89  	}
    90  	return w, nil
    91  }
    92  
    93  // Worker holds the result of some predicate regarding an entity's life,
    94  // and fails with ErrValueChanged when the result of the predicate changes.
    95  type Worker struct {
    96  	catacomb catacomb.Catacomb
    97  	config   Config
    98  	life     life.Value
    99  }
   100  
   101  // Kill is part of the worker.Worker interface.
   102  func (w *Worker) Kill() {
   103  	w.catacomb.Kill(nil)
   104  }
   105  
   106  // Wait is part of the worker.Worker interface.
   107  func (w *Worker) Wait() error {
   108  	return w.catacomb.Wait()
   109  }
   110  
   111  // Check is part of the util.Flag interface.
   112  func (w *Worker) Check() bool {
   113  	return w.config.Result(w.life)
   114  }
   115  
   116  func (w *Worker) alwaysDead() error {
   117  	if w.config.Result(life.Dead) != w.Check() {
   118  		return ErrValueChanged
   119  	}
   120  	<-w.catacomb.Dying()
   121  	return w.catacomb.ErrDying()
   122  }
   123  
   124  func (w *Worker) loop() error {
   125  	watcher, err := w.config.Facade.Watch(w.config.Entity)
   126  	if err != nil {
   127  		return errors.Trace(err)
   128  	}
   129  	if err := w.catacomb.Add(watcher); err != nil {
   130  		return errors.Trace(err)
   131  	}
   132  	for {
   133  		select {
   134  		case <-w.catacomb.Dying():
   135  			return w.catacomb.ErrDying()
   136  		case <-watcher.Changes():
   137  			l, err := w.config.Facade.Life(w.config.Entity)
   138  			if w.config.NotFoundIsDead && errors.Is(err, ErrNotFound) {
   139  				l = life.Dead
   140  			} else if err != nil {
   141  				return errors.Trace(err)
   142  			}
   143  			if w.config.Result(l) != w.Check() {
   144  				return ErrValueChanged
   145  			}
   146  		}
   147  	}
   148  }