github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/environ/environ.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package environ 5 6 import ( 7 "context" 8 "reflect" 9 10 "github.com/juju/errors" 11 "github.com/juju/worker/v3/catacomb" 12 13 "github.com/juju/juju/core/watcher" 14 "github.com/juju/juju/environs" 15 environscloudspec "github.com/juju/juju/environs/cloudspec" 16 ) 17 18 // logger is here to stop the desire of creating a package level logger. 19 // Don't do this, instead use the one passed as manifold config. 20 type logger interface{} 21 22 var _ logger = struct{}{} 23 24 // ConfigObserver exposes a model configuration and a watch constructor 25 // that allows clients to be informed of changes to the configuration. 26 type ConfigObserver interface { 27 environs.EnvironConfigGetter 28 WatchForModelConfigChanges() (watcher.NotifyWatcher, error) 29 WatchCloudSpecChanges() (watcher.NotifyWatcher, error) 30 } 31 32 // Config describes the dependencies of a Tracker. 33 // 34 // It's arguable that it should be called TrackerConfig, because of the heavy 35 // use of model config in this package. 36 type Config struct { 37 Observer ConfigObserver 38 NewEnvironFunc environs.NewEnvironFunc 39 Logger Logger 40 } 41 42 // Validate returns an error if the config cannot be used to start a Tracker. 43 func (config Config) Validate() error { 44 if config.Observer == nil { 45 return errors.NotValidf("nil Observer") 46 } 47 if config.NewEnvironFunc == nil { 48 return errors.NotValidf("nil NewEnvironFunc") 49 } 50 if config.Logger == nil { 51 return errors.NotValidf("nil Logger") 52 } 53 return nil 54 } 55 56 // Tracker loads an environment, makes it available to clients, and updates 57 // the environment in response to config changes until it is killed. 58 type Tracker struct { 59 config Config 60 catacomb catacomb.Catacomb 61 environ environs.Environ 62 currentCloudSpec environscloudspec.CloudSpec 63 } 64 65 // NewTracker loads an environment from the observer and returns a new Tracker, 66 // or an error if anything goes wrong. If a tracker is returned, its Environ() 67 // method is immediately usable. 68 // 69 // The caller is responsible for Kill()ing the returned Tracker and Wait()ing 70 // for any errors it might return. 71 func NewTracker(config Config) (*Tracker, error) { 72 if err := config.Validate(); err != nil { 73 return nil, errors.Trace(err) 74 } 75 76 environ, spec, err := environs.GetEnvironAndCloud(config.Observer, config.NewEnvironFunc) 77 if err != nil { 78 return nil, errors.Trace(err) 79 } 80 81 t := &Tracker{ 82 config: config, 83 environ: environ, 84 currentCloudSpec: *spec, 85 } 86 err = catacomb.Invoke(catacomb.Plan{ 87 Site: &t.catacomb, 88 Work: t.loop, 89 }) 90 if err != nil { 91 return nil, errors.Trace(err) 92 } 93 return t, nil 94 } 95 96 // Environ returns the encapsulated Environ. It will continue to be updated in 97 // the background for as long as the Tracker continues to run. 98 func (t *Tracker) Environ() environs.Environ { 99 return t.environ 100 } 101 102 func (t *Tracker) loop() (err error) { 103 cfg := t.environ.Config() 104 defer errors.DeferredAnnotatef(&err, "model %q (%s)", cfg.Name(), cfg.UUID()) 105 106 logger := t.config.Logger 107 environWatcher, err := t.config.Observer.WatchForModelConfigChanges() 108 if err != nil { 109 return errors.Annotate(err, "watching environ config") 110 } 111 if err := t.catacomb.Add(environWatcher); err != nil { 112 return errors.Trace(err) 113 } 114 115 // Some environs support reacting to changes in the cloud config. 116 // Set up a watcher if that's the case. 117 var ( 118 cloudWatcherChanges watcher.NotifyChannel 119 cloudSpecSetter environs.CloudSpecSetter 120 ok bool 121 ) 122 if cloudSpecSetter, ok = t.environ.(environs.CloudSpecSetter); !ok { 123 logger.Warningf("cloud type %v doesn't support dynamic changing of cloud spec", t.environ.Config().Type()) 124 } else { 125 cloudWatcher, err := t.config.Observer.WatchCloudSpecChanges() 126 if err != nil { 127 return errors.Annotate(err, "cannot watch environ cloud spec") 128 } 129 if err := t.catacomb.Add(cloudWatcher); err != nil { 130 return errors.Trace(err) 131 } 132 cloudWatcherChanges = cloudWatcher.Changes() 133 } 134 for { 135 logger.Debugf("waiting for environ watch notification") 136 select { 137 case <-t.catacomb.Dying(): 138 return t.catacomb.ErrDying() 139 case _, ok := <-environWatcher.Changes(): 140 if !ok { 141 return errors.New("environ config watch closed") 142 } 143 logger.Debugf("reloading environ config") 144 modelConfig, err := t.config.Observer.ModelConfig() 145 if err != nil { 146 return errors.Annotate(err, "reading model config") 147 } 148 if err = t.environ.SetConfig(modelConfig); err != nil { 149 return errors.Annotate(err, "updating environ config") 150 } 151 case _, ok := <-cloudWatcherChanges: 152 if !ok { 153 return errors.New("cloud watch closed") 154 } 155 cloudSpec, err := t.config.Observer.CloudSpec() 156 if err != nil { 157 return errors.Annotate(err, "reading environ cloud spec") 158 } 159 if reflect.DeepEqual(cloudSpec, t.currentCloudSpec) { 160 continue 161 } 162 logger.Debugf("reloading cloud config") 163 if err = cloudSpecSetter.SetCloudSpec(context.TODO(), cloudSpec); err != nil { 164 return errors.Annotate(err, "cannot update environ cloud spec") 165 } 166 t.currentCloudSpec = cloudSpec 167 } 168 } 169 } 170 171 // Kill is part of the worker.Worker interface. 172 func (t *Tracker) Kill() { 173 t.catacomb.Kill(nil) 174 } 175 176 // Wait is part of the worker.Worker interface. 177 func (t *Tracker) Wait() error { 178 return t.catacomb.Wait() 179 }