github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/stateconfigwatcher/manifold.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package stateconfigwatcher
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"github.com/juju/utils/v3/voyeur"
    10  	"github.com/juju/worker/v3"
    11  	"github.com/juju/worker/v3/dependency"
    12  	"gopkg.in/tomb.v2"
    13  
    14  	"github.com/juju/juju/agent"
    15  	apiagent "github.com/juju/juju/api/agent/agent"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.worker.stateconfigwatcher")
    19  
    20  type ManifoldConfig struct {
    21  	AgentName          string
    22  	AgentConfigChanged *voyeur.Value
    23  }
    24  
    25  // Manifold returns a dependency.Manifold which wraps the machine
    26  // agent's voyeur.Value which gets set whenever the machine agent's
    27  // config is changed. Whenever the config is updated the presence of
    28  // state serving info is checked and if state serving info was added
    29  // or removed the manifold worker will bounce itself.
    30  //
    31  // The manifold offers a single boolean output which will be true if
    32  // state serving info is available (i.e. the machine agent should be a
    33  // state server) and false otherwise.
    34  //
    35  // This manifold is intended to be used as a dependency for the state
    36  // manifold.
    37  func Manifold(config ManifoldConfig) dependency.Manifold {
    38  	return dependency.Manifold{
    39  		Inputs: []string{config.AgentName},
    40  		Start: func(context dependency.Context) (worker.Worker, error) {
    41  			var a agent.Agent
    42  			if err := context.Get(config.AgentName, &a); err != nil {
    43  				return nil, err
    44  			}
    45  
    46  			if config.AgentConfigChanged == nil {
    47  				return nil, errors.NotValidf("nil AgentConfigChanged")
    48  			}
    49  
    50  			tagKind := a.CurrentConfig().Tag().Kind()
    51  			if !apiagent.IsAllowedControllerTag(tagKind) {
    52  				return nil, errors.New("manifold can only be used with a machine or controller agent")
    53  			}
    54  
    55  			w := &stateConfigWatcher{
    56  				agent:              a,
    57  				agentConfigChanged: config.AgentConfigChanged,
    58  			}
    59  			w.tomb.Go(w.loop)
    60  			return w, nil
    61  		},
    62  		Output: outputFunc,
    63  	}
    64  }
    65  
    66  // outputFunc extracts a bool from a *stateConfigWatcher. If true, the
    67  // agent is a state server.
    68  func outputFunc(in worker.Worker, out interface{}) error {
    69  	inWorker, _ := in.(*stateConfigWatcher)
    70  	if inWorker == nil {
    71  		return errors.Errorf("in should be a %T; got %T", inWorker, in)
    72  	}
    73  	switch outPointer := out.(type) {
    74  	case *bool:
    75  		*outPointer = inWorker.isStateServer()
    76  	default:
    77  		return errors.Errorf("out should be *bool; got %T", out)
    78  	}
    79  	return nil
    80  }
    81  
    82  type stateConfigWatcher struct {
    83  	tomb               tomb.Tomb
    84  	agent              agent.Agent
    85  	agentConfigChanged *voyeur.Value
    86  }
    87  
    88  func (w *stateConfigWatcher) isStateServer() bool {
    89  	config := w.agent.CurrentConfig()
    90  	_, ok := config.StateServingInfo()
    91  	return ok
    92  }
    93  
    94  func (w *stateConfigWatcher) loop() error {
    95  	watch := w.agentConfigChanged.Watch()
    96  	defer watch.Close()
    97  
    98  	lastValue := w.isStateServer()
    99  
   100  	watchCh := make(chan bool)
   101  	go func() {
   102  		for {
   103  			if watch.Next() {
   104  				select {
   105  				case <-w.tomb.Dying():
   106  					return
   107  				case watchCh <- true:
   108  				}
   109  			} else {
   110  				// watcher or voyeur.Value closed.
   111  				close(watchCh)
   112  				return
   113  			}
   114  		}
   115  	}()
   116  
   117  	for {
   118  		select {
   119  		case <-w.tomb.Dying():
   120  			logger.Infof("tomb dying")
   121  			return tomb.ErrDying
   122  		case _, ok := <-watchCh:
   123  			if !ok {
   124  				return errors.New("config changed value closed")
   125  			}
   126  			if w.isStateServer() != lastValue {
   127  				// State serving info has been set or unset so restart
   128  				// so that dependents get notified. ErrBounce ensures
   129  				// that the manifold is restarted quickly.
   130  				logger.Debugf("state serving info change in agent config")
   131  				return dependency.ErrBounce
   132  			}
   133  		}
   134  	}
   135  }
   136  
   137  // Kill implements worker.Worker.
   138  func (w *stateConfigWatcher) Kill() {
   139  	w.tomb.Kill(nil)
   140  }
   141  
   142  // Wait implements worker.Worker.
   143  func (w *stateConfigWatcher) Wait() error {
   144  	return w.tomb.Wait()
   145  }