github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/apiconfigwatcher/manifold.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiconfigwatcher 5 6 import ( 7 "sort" 8 9 "github.com/juju/errors" 10 "github.com/juju/utils/v3/voyeur" 11 "github.com/juju/worker/v3" 12 "github.com/juju/worker/v3/dependency" 13 "gopkg.in/tomb.v2" 14 15 "github.com/juju/juju/agent" 16 ) 17 18 // Logger represents the methods used by the worker to log information. 19 type Logger interface { 20 Debugf(string, ...interface{}) 21 Errorf(string, ...interface{}) 22 } 23 24 // logger is here to stop the desire of creating a package level logger. 25 // Don't do this, instead use the one passed as manifold config. 26 type logger interface{} 27 28 var _ logger = struct{}{} 29 30 type ManifoldConfig struct { 31 AgentName string 32 AgentConfigChanged *voyeur.Value 33 Logger Logger 34 } 35 36 // Manifold returns a dependency.Manifold which wraps an agent's 37 // voyeur.Value which is set whenever the agent config is 38 // changed. When the API server addresses in the config change the 39 // manifold will bounce itself. 40 // 41 // The manifold is intended to be a dependency for the api-caller 42 // manifold and is required to support model migrations. 43 func Manifold(config ManifoldConfig) dependency.Manifold { 44 return dependency.Manifold{ 45 Inputs: []string{config.AgentName}, 46 Start: func(context dependency.Context) (worker.Worker, error) { 47 if config.AgentConfigChanged == nil { 48 return nil, errors.NotValidf("nil AgentConfigChanged") 49 } 50 51 var a agent.Agent 52 if err := context.Get(config.AgentName, &a); err != nil { 53 return nil, err 54 } 55 56 w := &apiconfigwatcher{ 57 agent: a, 58 agentConfigChanged: config.AgentConfigChanged, 59 logger: config.Logger, 60 } 61 w.tomb.Go(w.loop) 62 return w, nil 63 }, 64 } 65 } 66 67 type apiconfigwatcher struct { 68 tomb tomb.Tomb 69 agent agent.Agent 70 agentConfigChanged *voyeur.Value 71 addrs []string 72 logger Logger 73 } 74 75 func (w *apiconfigwatcher) loop() error { 76 w.addrs = w.getAPIAddresses() 77 watch := w.agentConfigChanged.Watch() 78 defer watch.Close() 79 80 // TODO(mjs) - this is pretty awful. There should be a 81 // NotifyWatcher for voyeur.Value. Note also that this code is 82 // repeated elsewhere. 83 watchCh := make(chan bool) 84 go func() { 85 for { 86 if watch.Next() { 87 select { 88 case <-w.tomb.Dying(): 89 return 90 case watchCh <- true: 91 } 92 } else { 93 // watcher or voyeur.Value closed. 94 close(watchCh) 95 return 96 } 97 } 98 }() 99 100 for { 101 // Always unconditionally check for a change in API addresses 102 // first, in case there was a change between the start func 103 // and the call to Watch. 104 if !stringSliceEq(w.addrs, w.getAPIAddresses()) { 105 w.logger.Debugf("API addresses changed in agent config") 106 return dependency.ErrBounce 107 } 108 109 select { 110 case <-w.tomb.Dying(): 111 return tomb.ErrDying 112 case _, ok := <-watchCh: 113 if !ok { 114 return errors.New("config changed value closed") 115 } 116 } 117 } 118 } 119 120 // Kill implements worker.Worker. 121 func (w *apiconfigwatcher) Kill() { 122 w.tomb.Kill(nil) 123 } 124 125 // Wait implements worker.Worker. 126 func (w *apiconfigwatcher) Wait() error { 127 return w.tomb.Wait() 128 } 129 130 func (w *apiconfigwatcher) getAPIAddresses() []string { 131 config := w.agent.CurrentConfig() 132 addrs, err := config.APIAddresses() 133 if err != nil { 134 w.logger.Errorf("retrieving API addresses: %s", err) 135 addrs = nil 136 } 137 sort.Strings(addrs) 138 return addrs 139 } 140 141 func stringSliceEq(a, b []string) bool { 142 if a == nil && b == nil { 143 return true 144 } 145 146 if a == nil || b == nil { 147 return false 148 } 149 150 if len(a) != len(b) { 151 return false 152 } 153 154 for i := range a { 155 if a[i] != b[i] { 156 return false 157 } 158 } 159 return true 160 }