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