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  }