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  }