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 }