github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/externalcontrollerupdater/externalcontrollerupdater.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package externalcontrollerupdater 5 6 import ( 7 "io" 8 "reflect" 9 "time" 10 11 "github.com/juju/clock" 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 "github.com/juju/names/v5" 15 "github.com/juju/worker/v3" 16 "github.com/juju/worker/v3/catacomb" 17 18 "github.com/juju/juju/api" 19 "github.com/juju/juju/api/controller/crosscontroller" 20 "github.com/juju/juju/core/crossmodel" 21 "github.com/juju/juju/core/watcher" 22 "github.com/juju/juju/rpc" 23 ) 24 25 var logger = loggo.GetLogger("juju.worker.externalcontrollerupdater") 26 27 // ExternalControllerUpdaterClient defines the interface for watching changes 28 // to the local controller's external controller records, and obtaining and 29 // updating their values. This will communicate only with the local controller. 30 type ExternalControllerUpdaterClient interface { 31 WatchExternalControllers() (watcher.StringsWatcher, error) 32 ExternalControllerInfo(controllerUUID string) (*crossmodel.ControllerInfo, error) 33 SetExternalControllerInfo(crossmodel.ControllerInfo) error 34 } 35 36 // ExternalControllerWatcherClientCloser extends the ExternalControllerWatcherClient 37 // interface with a Close method, for closing the API connection associated with 38 // the client. 39 type ExternalControllerWatcherClientCloser interface { 40 ExternalControllerWatcherClient 41 io.Closer 42 } 43 44 // ExternalControllerWatcherClient defines the interface for watching changes 45 // to and obtaining the current API information for a controller. This will 46 // communicate with an external controller. 47 type ExternalControllerWatcherClient interface { 48 WatchControllerInfo() (watcher.NotifyWatcher, error) 49 ControllerInfo() (*crosscontroller.ControllerInfo, error) 50 } 51 52 // NewExternalControllerWatcherClientFunc is a function type that 53 // returns an ExternalControllerWatcherClientCloser, given an 54 // *api.Info. The api.Info should be for making a controller-only 55 // connection to a remote/external controller. 56 type NewExternalControllerWatcherClientFunc func(*api.Info) (ExternalControllerWatcherClientCloser, error) 57 58 // New returns a new external controller updater worker. 59 func New( 60 externalControllers ExternalControllerUpdaterClient, 61 newExternalControllerWatcherClient NewExternalControllerWatcherClientFunc, 62 clock clock.Clock, 63 ) (worker.Worker, error) { 64 w := updaterWorker{ 65 watchExternalControllers: externalControllers.WatchExternalControllers, 66 externalControllerInfo: externalControllers.ExternalControllerInfo, 67 setExternalControllerInfo: externalControllers.SetExternalControllerInfo, 68 newExternalControllerWatcherClient: newExternalControllerWatcherClient, 69 runner: worker.NewRunner(worker.RunnerParams{ 70 // One of the controller watchers fails should not 71 // prevent the others from running. 72 IsFatal: func(error) bool { return false }, 73 74 // If the API connection fails, try again in 1 minute. 75 RestartDelay: time.Minute, 76 Clock: clock, 77 Logger: logger, 78 }), 79 } 80 if err := catacomb.Invoke(catacomb.Plan{ 81 Site: &w.catacomb, 82 Work: w.loop, 83 Init: []worker.Worker{w.runner}, 84 }); err != nil { 85 return nil, errors.Trace(err) 86 } 87 return &w, nil 88 } 89 90 type updaterWorker struct { 91 catacomb catacomb.Catacomb 92 runner *worker.Runner 93 94 watchExternalControllers func() (watcher.StringsWatcher, error) 95 externalControllerInfo func(controllerUUID string) (*crossmodel.ControllerInfo, error) 96 setExternalControllerInfo func(crossmodel.ControllerInfo) error 97 newExternalControllerWatcherClient NewExternalControllerWatcherClientFunc 98 } 99 100 // Kill is part of the worker.Worker interface. 101 func (w *updaterWorker) Kill() { 102 w.catacomb.Kill(nil) 103 } 104 105 // Wait is part of the worker.Worker interface. 106 func (w *updaterWorker) Wait() error { 107 return w.catacomb.Wait() 108 } 109 110 func (w *updaterWorker) loop() error { 111 watcher, err := w.watchExternalControllers() 112 if err != nil { 113 return errors.Annotate(err, "watching external controllers") 114 } 115 _ = w.catacomb.Add(watcher) 116 117 watchers := names.NewSet() 118 for { 119 select { 120 case <-w.catacomb.Dying(): 121 return w.catacomb.ErrDying() 122 123 case ids, ok := <-watcher.Changes(): 124 if !ok { 125 return w.catacomb.ErrDying() 126 } 127 128 if len(ids) == 0 { 129 continue 130 } 131 132 logger.Debugf("external controllers changed: %q", ids) 133 tags := make([]names.ControllerTag, len(ids)) 134 for i, id := range ids { 135 if !names.IsValidController(id) { 136 return errors.Errorf("%q is not a valid controller tag", id) 137 } 138 tags[i] = names.NewControllerTag(id) 139 } 140 141 for _, tag := range tags { 142 // We're informed when an external controller 143 // is added or removed, so treat as a toggle. 144 if watchers.Contains(tag) { 145 logger.Infof("stopping watcher for external controller %q", tag.Id()) 146 _ = w.runner.StopAndRemoveWorker(tag.Id(), w.catacomb.Dying()) 147 watchers.Remove(tag) 148 continue 149 } 150 logger.Infof("starting watcher for external controller %q", tag.Id()) 151 watchers.Add(tag) 152 if err := w.runner.StartWorker(tag.Id(), func() (worker.Worker, error) { 153 cw := controllerWatcher{ 154 tag: tag, 155 setExternalControllerInfo: w.setExternalControllerInfo, 156 externalControllerInfo: w.externalControllerInfo, 157 newExternalControllerWatcherClient: w.newExternalControllerWatcherClient, 158 } 159 if err := catacomb.Invoke(catacomb.Plan{ 160 Site: &cw.catacomb, 161 Work: cw.loop, 162 }); err != nil { 163 return nil, errors.Trace(err) 164 } 165 return &cw, nil 166 }); err != nil { 167 return errors.Annotatef(err, "starting watcher for external controller %q", tag.Id()) 168 } 169 } 170 } 171 } 172 } 173 174 // controllerWatcher is a worker that watches for changes to the external 175 // controller with the given tag. The external controller must be known 176 // to the local controller. 177 type controllerWatcher struct { 178 catacomb catacomb.Catacomb 179 180 tag names.ControllerTag 181 setExternalControllerInfo func(crossmodel.ControllerInfo) error 182 externalControllerInfo func(controllerUUID string) (*crossmodel.ControllerInfo, error) 183 newExternalControllerWatcherClient NewExternalControllerWatcherClientFunc 184 } 185 186 // Kill is part of the worker.Worker interface. 187 func (w *controllerWatcher) Kill() { 188 w.catacomb.Kill(nil) 189 } 190 191 // Wait is part of the worker.Worker interface. 192 func (w *controllerWatcher) Wait() error { 193 err := w.catacomb.Wait() 194 if errors.Cause(err) == rpc.ErrShutdown { 195 // RPC shutdown errors need to be ignored. 196 return nil 197 } 198 return err 199 } 200 201 func (w *controllerWatcher) loop() error { 202 // We get the API info from the local controller initially. 203 info, err := w.externalControllerInfo(w.tag.Id()) 204 if errors.IsNotFound(err) { 205 return nil 206 } else if err != nil { 207 return errors.Annotate(err, "getting cached external controller info") 208 } 209 logger.Debugf("controller info for controller %q: %v", w.tag.Id(), info) 210 211 var nw watcher.NotifyWatcher 212 var client ExternalControllerWatcherClientCloser 213 defer func() { 214 if client != nil { 215 _ = client.Close() 216 } 217 }() 218 219 for { 220 if client == nil { 221 apiInfo := &api.Info{ 222 Addrs: info.Addrs, 223 CACert: info.CACert, 224 Tag: names.NewUserTag(api.AnonymousUsername), 225 } 226 client, nw, err = w.connectAndWatch(apiInfo, w.catacomb.Dying()) 227 if err == w.catacomb.ErrDying() { 228 return err 229 } else if err != nil { 230 return errors.Trace(err) 231 } 232 _ = w.catacomb.Add(nw) 233 } 234 235 select { 236 case <-w.catacomb.Dying(): 237 return w.catacomb.ErrDying() 238 case _, ok := <-nw.Changes(): 239 if !ok { 240 return w.catacomb.ErrDying() 241 } 242 243 newInfo, err := client.ControllerInfo() 244 if err != nil { 245 return errors.Annotate(err, "getting external controller info") 246 } 247 if reflect.DeepEqual(newInfo.Addrs, info.Addrs) { 248 continue 249 } 250 // API addresses have changed. Save the details to the 251 // local controller and stop the existing notify watcher 252 // and set it to nil, so we'll restart it with the new 253 // addresses. 254 info.Addrs = newInfo.Addrs 255 if err := w.setExternalControllerInfo(crossmodel.ControllerInfo{ 256 ControllerTag: w.tag, 257 Alias: info.Alias, 258 Addrs: info.Addrs, 259 CACert: info.CACert, 260 }); err != nil { 261 return errors.Annotate(err, "caching external controller info") 262 } 263 logger.Infof("new controller info for controller %q: %v", w.tag.Id(), info) 264 if err := worker.Stop(nw); err != nil { 265 return errors.Trace(err) 266 } 267 if err := client.Close(); err != nil { 268 return errors.Trace(err) 269 } 270 client = nil 271 nw = nil 272 } 273 } 274 } 275 276 // connectAndWatch connects to the specified controller and watches for changes. 277 // It aborts if signalled, which prevents the watcher loop from blocking any shutdown 278 // of the watcher the may be requested by the parent worker. 279 func (w *controllerWatcher) connectAndWatch(apiInfo *api.Info, abort <-chan struct{}) (ExternalControllerWatcherClientCloser, watcher.NotifyWatcher, error) { 280 type result struct { 281 client ExternalControllerWatcherClientCloser 282 nw watcher.NotifyWatcher 283 } 284 donec := make(chan result, 1) 285 errc := make(chan error, 1) 286 go func() { 287 client, err := w.newExternalControllerWatcherClient(apiInfo) 288 if err != nil { 289 errc <- errors.Annotate(err, "getting external controller client") 290 return 291 } 292 nw, err := client.WatchControllerInfo() 293 if err != nil { 294 errc <- errors.Annotate(err, "watching external controller") 295 return 296 } 297 donec <- result{client, nw} 298 }() 299 select { 300 case <-abort: 301 return nil, nil, w.catacomb.ErrDying() 302 case err := <-errc: 303 return nil, nil, errors.Trace(err) 304 case r := <-donec: 305 return r.client, r.nw, nil 306 } 307 }