github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/caasunitprovisioner/worker.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // This worker is responsible for watching for scale changes in the number of 5 // units and scaling out applications. It's also responsible for reporting the 6 // service info (such as IP addresses) of unit pods. 7 8 package caasunitprovisioner 9 10 import ( 11 "sync" 12 13 "github.com/juju/charm/v12" 14 "github.com/juju/errors" 15 "github.com/juju/worker/v3" 16 "github.com/juju/worker/v3/catacomb" 17 18 "github.com/juju/juju/core/life" 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 // Config holds configuration for the CAAS unit provisioner worker. 28 type Config struct { 29 ApplicationGetter ApplicationGetter 30 ApplicationUpdater ApplicationUpdater 31 ServiceBroker ServiceBroker 32 33 ContainerBroker ContainerBroker 34 ProvisioningInfoGetter ProvisioningInfoGetter 35 ProvisioningStatusSetter ProvisioningStatusSetter 36 LifeGetter LifeGetter 37 UnitUpdater UnitUpdater 38 CharmGetter CharmGetter 39 40 Logger Logger 41 } 42 43 // Validate validates the worker configuration. 44 func (config Config) Validate() error { 45 if config.ApplicationGetter == nil { 46 return errors.NotValidf("missing ApplicationGetter") 47 } 48 if config.ApplicationUpdater == nil { 49 return errors.NotValidf("missing ApplicationUpdater") 50 } 51 if config.ServiceBroker == nil { 52 return errors.NotValidf("missing ServiceBroker") 53 } 54 if config.ContainerBroker == nil { 55 return errors.NotValidf("missing ContainerBroker") 56 } 57 if config.ProvisioningInfoGetter == nil { 58 return errors.NotValidf("missing ProvisioningInfoGetter") 59 } 60 if config.LifeGetter == nil { 61 return errors.NotValidf("missing LifeGetter") 62 } 63 if config.UnitUpdater == nil { 64 return errors.NotValidf("missing UnitUpdater") 65 } 66 if config.ProvisioningStatusSetter == nil { 67 return errors.NotValidf("missing ProvisioningStatusSetter") 68 } 69 if config.CharmGetter == nil { 70 return errors.NotValidf("missing CharmGetter") 71 } 72 if config.Logger == nil { 73 return errors.NotValidf("missing Logger") 74 } 75 return nil 76 } 77 78 // NewWorker starts and returns a new CAAS unit provisioner worker. 79 func NewWorker(config Config) (worker.Worker, error) { 80 if err := config.Validate(); err != nil { 81 return nil, errors.Trace(err) 82 } 83 p := &provisioner{config: config} 84 err := catacomb.Invoke(catacomb.Plan{ 85 Site: &p.catacomb, 86 Work: p.loop, 87 }) 88 return p, err 89 } 90 91 type provisioner struct { 92 catacomb catacomb.Catacomb 93 config Config 94 95 // appWorkers holds the worker created to manage each application. 96 // It's defined here so that we can access it in tests. 97 appWorkers map[string]*applicationWorker 98 mu sync.Mutex 99 } 100 101 // Kill is part of the worker.Worker interface. 102 func (p *provisioner) Kill() { 103 p.catacomb.Kill(nil) 104 } 105 106 // Wait is part of the worker.Worker interface. 107 func (p *provisioner) Wait() error { 108 return p.catacomb.Wait() 109 } 110 111 // These helper methods protect the appWorkers map so we can access for testing. 112 113 func (p *provisioner) saveApplicationWorker(appName string, aw *applicationWorker) { 114 p.mu.Lock() 115 defer p.mu.Unlock() 116 117 if p.appWorkers == nil { 118 p.appWorkers = make(map[string]*applicationWorker) 119 } 120 p.appWorkers[appName] = aw 121 } 122 123 func (p *provisioner) deleteApplicationWorker(appName string) { 124 p.mu.Lock() 125 defer p.mu.Unlock() 126 127 delete(p.appWorkers, appName) 128 } 129 130 func (p *provisioner) getApplicationWorker(appName string) (*applicationWorker, bool) { 131 p.mu.Lock() 132 defer p.mu.Unlock() 133 134 if len(p.appWorkers) == 0 { 135 return nil, false 136 } 137 aw, ok := p.appWorkers[appName] 138 return aw, ok 139 } 140 141 func (p *provisioner) loop() error { 142 logger := p.config.Logger 143 w, err := p.config.ApplicationGetter.WatchApplications() 144 if err != nil { 145 return errors.Trace(err) 146 } 147 if err := p.catacomb.Add(w); err != nil { 148 return errors.Trace(err) 149 } 150 151 for { 152 select { 153 case <-p.catacomb.Dying(): 154 return p.catacomb.ErrDying() 155 case apps, ok := <-w.Changes(): 156 if !ok { 157 return errors.New("watcher closed channel") 158 } 159 for _, appName := range apps { 160 // If charm is (now) a v2 charm, skip processing. 161 format, err := p.charmFormat(appName) 162 if errors.IsNotFound(err) { 163 p.config.Logger.Debugf("application %q no longer exists", appName) 164 continue 165 } else if err != nil { 166 return errors.Trace(err) 167 } 168 if format >= charm.FormatV2 { 169 p.config.Logger.Tracef("v1 unit provisioner got event for v2 app %q, skipping", appName) 170 continue 171 } 172 173 appLife, err := p.config.LifeGetter.Life(appName) 174 if err != nil && !errors.IsNotFound(err) { 175 return errors.Trace(err) 176 } 177 if err != nil || appLife == life.Dead { 178 // Once an application is deleted, remove the k8s service and ingress resources. 179 if err := p.config.ServiceBroker.UnexposeService(appName); err != nil { 180 return errors.Trace(err) 181 } 182 if err := p.config.ServiceBroker.DeleteService(appName); err != nil { 183 return errors.Trace(err) 184 } 185 w, ok := p.getApplicationWorker(appName) 186 if ok { 187 // Before stopping the application worker, inform it that 188 // the app is gone so it has a chance to clean up. 189 // The worker will act on the removal prior to processing the 190 // Stop() request. 191 if err := worker.Stop(w); err != nil { 192 logger.Errorf("stopping application worker for %v: %v", appName, err) 193 } 194 p.deleteApplicationWorker(appName) 195 } 196 // Start the application undertaker worker to watch the cluster 197 // and wait for resources to be cleaned up. 198 mode, err := p.config.ApplicationGetter.DeploymentMode(appName) 199 if err != nil { 200 return errors.Trace(err) 201 } 202 uw, err := newApplicationUndertaker( 203 appName, 204 mode, 205 p.config.ServiceBroker, 206 p.config.ContainerBroker, 207 p.config.ApplicationUpdater, 208 logger, 209 ) 210 if err != nil { 211 return errors.Trace(err) 212 } 213 _ = p.catacomb.Add(uw) 214 continue 215 } 216 if _, ok := p.getApplicationWorker(appName); ok || appLife == life.Dead { 217 // Already watching the application. or we're 218 // not yet watching it and it's dead. 219 continue 220 } 221 mode, err := p.config.ApplicationGetter.DeploymentMode(appName) 222 if err != nil { 223 return errors.Trace(err) 224 } 225 w, err := newApplicationWorker( 226 appName, 227 mode, 228 p.config.ServiceBroker, 229 p.config.ContainerBroker, 230 p.config.ProvisioningStatusSetter, 231 p.config.ProvisioningInfoGetter, 232 p.config.ApplicationGetter, 233 p.config.ApplicationUpdater, 234 p.config.UnitUpdater, 235 p.config.CharmGetter, 236 logger, 237 ) 238 if err != nil { 239 return errors.Trace(err) 240 } 241 p.saveApplicationWorker(appName, w) 242 _ = p.catacomb.Add(w) 243 } 244 } 245 } 246 } 247 248 func (p *provisioner) charmFormat(appName string) (charm.Format, error) { 249 charmInfo, err := p.config.CharmGetter.ApplicationCharmInfo(appName) 250 if err != nil { 251 return charm.FormatUnknown, errors.Annotatef(err, "failed to get charm info for application %q", appName) 252 } 253 return charm.MetaFormat(charmInfo.Charm()), nil 254 }