github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/voyeur" 10 "gopkg.in/juju/names.v2" 11 "gopkg.in/juju/worker.v1" 12 "gopkg.in/juju/worker.v1/dependency" 13 "gopkg.in/tomb.v2" 14 15 "github.com/juju/juju/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 it 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 offes 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 if _, ok := a.CurrentConfig().Tag().(names.MachineTag); !ok { 51 return nil, errors.New("manifold can only be used with a machine agent") 52 } 53 54 w := &stateConfigWatcher{ 55 agent: a, 56 agentConfigChanged: config.AgentConfigChanged, 57 } 58 w.tomb.Go(w.loop) 59 return w, nil 60 }, 61 Output: outputFunc, 62 } 63 } 64 65 // outputFunc extracts a bool from a *stateConfigWatcher. If true, the 66 // agent is a state server. 67 func outputFunc(in worker.Worker, out interface{}) error { 68 inWorker, _ := in.(*stateConfigWatcher) 69 if inWorker == nil { 70 return errors.Errorf("in should be a %T; got %T", inWorker, in) 71 } 72 switch outPointer := out.(type) { 73 case *bool: 74 *outPointer = inWorker.isStateServer() 75 default: 76 return errors.Errorf("out should be *bool; got %T", out) 77 } 78 return nil 79 } 80 81 type stateConfigWatcher struct { 82 tomb tomb.Tomb 83 agent agent.Agent 84 agentConfigChanged *voyeur.Value 85 } 86 87 func (w *stateConfigWatcher) isStateServer() bool { 88 config := w.agent.CurrentConfig() 89 _, ok := config.StateServingInfo() 90 return ok 91 } 92 93 func (w *stateConfigWatcher) loop() error { 94 watch := w.agentConfigChanged.Watch() 95 defer watch.Close() 96 97 lastValue := w.isStateServer() 98 99 watchCh := make(chan bool) 100 go func() { 101 for { 102 if watch.Next() { 103 select { 104 case <-w.tomb.Dying(): 105 return 106 case watchCh <- true: 107 } 108 } else { 109 // watcher or voyeur.Value closed. 110 close(watchCh) 111 return 112 } 113 } 114 }() 115 116 for { 117 select { 118 case <-w.tomb.Dying(): 119 logger.Infof("tomb dying") 120 return tomb.ErrDying 121 case _, ok := <-watchCh: 122 if !ok { 123 return errors.New("config changed value closed") 124 } 125 if w.isStateServer() != lastValue { 126 // State serving info has been set or unset so restart 127 // so that dependents get notified. ErrBounce ensures 128 // that the manifold is restarted quickly. 129 logger.Debugf("state serving info change in agent config") 130 return dependency.ErrBounce 131 } 132 } 133 } 134 } 135 136 // Kill implements worker.Worker. 137 func (w *stateConfigWatcher) Kill() { 138 w.tomb.Kill(nil) 139 } 140 141 // Wait implements worker.Worker. 142 func (w *stateConfigWatcher) Wait() error { 143 return w.tomb.Wait() 144 }