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