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