github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/storageprovisioner/caasworker.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storageprovisioner 5 6 import ( 7 "sync" 8 9 "github.com/juju/errors" 10 "github.com/juju/names/v5" 11 "github.com/juju/worker/v3" 12 "github.com/juju/worker/v3/catacomb" 13 14 "github.com/juju/juju/core/life" 15 "github.com/juju/juju/core/watcher" 16 "github.com/juju/juju/rpc/params" 17 ) 18 19 // ApplicationWatcher provides an interface for 20 // watching for the lifecycle state changes 21 // (including addition) of applications. 22 type ApplicationWatcher interface { 23 WatchApplications() (watcher.StringsWatcher, error) 24 } 25 26 // NewCaasWorker starts and returns a new CAAS storage provisioner worker. 27 // The worker provisions model scoped storage and also watches and starts 28 // provisioner workers to handle storage for application units. 29 func NewCaasWorker(config Config) (worker.Worker, error) { 30 if err := config.Validate(); err != nil { 31 return nil, errors.Trace(err) 32 } 33 p := &provisioner{config: config} 34 err := catacomb.Invoke(catacomb.Plan{ 35 Site: &p.catacomb, 36 Work: p.loop, 37 }) 38 return p, err 39 } 40 41 type provisioner struct { 42 catacomb catacomb.Catacomb 43 config Config 44 45 // provisioners holds the worker created to manage each application. 46 // It's defined here so that we can access it in tests. 47 provisioners map[string]worker.Worker 48 mu sync.Mutex 49 } 50 51 // Kill is part of the worker.Worker interface. 52 func (p *provisioner) Kill() { 53 p.catacomb.Kill(nil) 54 } 55 56 // Wait is part of the worker.Worker interface. 57 func (p *provisioner) Wait() error { 58 return p.catacomb.Wait() 59 } 60 61 // These helper methods protect the provisioners map so we can access for testing. 62 63 func (p *provisioner) saveApplicationWorker(appName string, aw worker.Worker) { 64 p.mu.Lock() 65 defer p.mu.Unlock() 66 67 if p.provisioners == nil { 68 p.provisioners = make(map[string]worker.Worker) 69 } 70 p.provisioners[appName] = aw 71 } 72 73 func (p *provisioner) deleteApplicationWorker(appName string) { 74 p.mu.Lock() 75 defer p.mu.Unlock() 76 77 delete(p.provisioners, appName) 78 } 79 80 func (p *provisioner) getApplicationWorker(appName string) (worker.Worker, bool) { 81 p.mu.Lock() 82 defer p.mu.Unlock() 83 84 if len(p.provisioners) == 0 { 85 return nil, false 86 } 87 aw, ok := p.provisioners[appName] 88 return aw, ok 89 } 90 91 func (p *provisioner) loop() error { 92 appsWatcher, err := p.config.Applications.WatchApplications() 93 if err != nil { 94 return errors.Trace(err) 95 } 96 if err := p.catacomb.Add(appsWatcher); err != nil { 97 return errors.Trace(err) 98 } 99 100 modelProvisioner, err := NewStorageProvisioner(p.config) 101 if err != nil { 102 return errors.Trace(err) 103 } 104 if err := p.catacomb.Add(modelProvisioner); err != nil { 105 return errors.Trace(err) 106 } 107 108 for { 109 select { 110 case <-p.catacomb.Dying(): 111 return p.catacomb.ErrDying() 112 case apps, ok := <-appsWatcher.Changes(): 113 if !ok { 114 return errors.New("watcher closed channel") 115 } 116 appTags := make([]names.Tag, len(apps)) 117 for i, appID := range apps { 118 appTags[i] = names.NewApplicationTag(appID) 119 } 120 appsLife, err := p.config.Life.Life(appTags) 121 if err != nil { 122 return errors.Annotate(err, "getting application life") 123 } 124 for i, appID := range apps { 125 appLifeResult := appsLife[i] 126 if appLifeResult.Error != nil && params.IsCodeNotFound(appLifeResult.Error) || appLifeResult.Life == life.Dead { 127 p.config.Logger.Debugf("app %v not found", appID) 128 if appWorker, ok := p.getApplicationWorker(appID); ok { 129 if err := worker.Stop(appWorker); err != nil { 130 p.config.Logger.Errorf("stopping application storage worker for %v: %v", appID, err) 131 } 132 p.deleteApplicationWorker(appID) 133 } 134 continue 135 } 136 if _, ok := p.getApplicationWorker(appID); ok { 137 // Already watching the application. 138 continue 139 } 140 cfg := p.config 141 cfg.Scope = appTags[i] 142 w, err := NewStorageProvisioner(cfg) 143 if err != nil { 144 return errors.Trace(err) 145 } 146 p.saveApplicationWorker(appID, w) 147 _ = p.catacomb.Add(w) 148 } 149 } 150 } 151 }