github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/caasfirewaller/application_worker.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package caasfirewaller
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/charm/v12"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names/v5"
    12  	"github.com/juju/worker/v3"
    13  	"github.com/juju/worker/v3/catacomb"
    14  
    15  	"github.com/juju/juju/environs/tags"
    16  )
    17  
    18  type applicationWorker struct {
    19  	catacomb          catacomb.Catacomb
    20  	controllerUUID    string
    21  	modelUUID         string
    22  	application       string
    23  	applicationGetter ApplicationGetter
    24  	serviceExposer    ServiceExposer
    25  	lifeGetter        LifeGetter
    26  	charmGetter       CharmGetter
    27  
    28  	initial           bool
    29  	previouslyExposed bool
    30  
    31  	logger Logger
    32  }
    33  
    34  func newApplicationWorker(
    35  	controllerUUID string,
    36  	modelUUID string,
    37  	application string,
    38  	applicationGetter ApplicationGetter,
    39  	applicationExposer ServiceExposer,
    40  	lifeGetter LifeGetter,
    41  	charmGetter CharmGetter,
    42  	logger Logger,
    43  ) (worker.Worker, error) {
    44  	w := &applicationWorker{
    45  		controllerUUID:    controllerUUID,
    46  		modelUUID:         modelUUID,
    47  		application:       application,
    48  		applicationGetter: applicationGetter,
    49  		serviceExposer:    applicationExposer,
    50  		lifeGetter:        lifeGetter,
    51  		charmGetter:       charmGetter,
    52  		initial:           true,
    53  		logger:            logger,
    54  	}
    55  	if err := catacomb.Invoke(catacomb.Plan{
    56  		Site: &w.catacomb,
    57  		Work: w.loop,
    58  	}); err != nil {
    59  		return nil, errors.Trace(err)
    60  	}
    61  	return w, nil
    62  }
    63  
    64  // Kill is part of the worker.Worker interface.
    65  func (w *applicationWorker) Kill() {
    66  	w.catacomb.Kill(nil)
    67  }
    68  
    69  // Wait is part of the worker.Worker interface.
    70  func (w *applicationWorker) Wait() error {
    71  	return w.catacomb.Wait()
    72  }
    73  
    74  func (w *applicationWorker) loop() (err error) {
    75  	defer func() {
    76  		// If the application has been deleted, we can return nil.
    77  		if errors.IsNotFound(err) {
    78  			w.logger.Debugf("caas firewaller application %v has been removed", w.application)
    79  			err = nil
    80  		}
    81  	}()
    82  	appWatcher, err := w.applicationGetter.WatchApplication(w.application)
    83  	if err != nil {
    84  		return errors.Trace(err)
    85  	}
    86  	if err := w.catacomb.Add(appWatcher); err != nil {
    87  		return errors.Trace(err)
    88  	}
    89  
    90  	for {
    91  		select {
    92  		case <-w.catacomb.Dying():
    93  			return w.catacomb.ErrDying()
    94  		case _, ok := <-appWatcher.Changes():
    95  			if !ok {
    96  				return errors.New("application watcher closed")
    97  			}
    98  
    99  			// If charm is (now) a v2 charm, exit the worker.
   100  			format, err := w.charmFormat()
   101  			if errors.IsNotFound(err) {
   102  				w.logger.Debugf("application %q no longer exists", w.application)
   103  				return nil
   104  			} else if err != nil {
   105  				return errors.Trace(err)
   106  			}
   107  			if format >= charm.FormatV2 {
   108  				w.logger.Debugf("application %q v1 worker got v2 charm event, stopping", w.application)
   109  				return nil
   110  			}
   111  
   112  			if err := w.processApplicationChange(); err != nil {
   113  				if strings.Contains(err.Error(), "unexpected EOF") {
   114  					return nil
   115  				}
   116  				return errors.Trace(err)
   117  			}
   118  		}
   119  	}
   120  }
   121  
   122  func (w *applicationWorker) charmFormat() (charm.Format, error) {
   123  	charmInfo, err := w.charmGetter.ApplicationCharmInfo(w.application)
   124  	if err != nil {
   125  		return charm.FormatUnknown, errors.Annotatef(err, "failed to get charm info for application %q", w.application)
   126  	}
   127  	return charm.MetaFormat(charmInfo.Charm()), nil
   128  }
   129  
   130  func (w *applicationWorker) processApplicationChange() (err error) {
   131  	defer func() {
   132  		// Not found could be because the app got removed or there's
   133  		// no container service created yet as the app is still being set up.
   134  		if errors.IsNotFound(err) {
   135  			// Perhaps the app got removed while we were processing.
   136  			if _, err2 := w.lifeGetter.Life(w.application); err2 != nil {
   137  				err = err2
   138  				return
   139  			}
   140  			// Ignore not found error because the ip could be not ready yet at this stage.
   141  			w.logger.Warningf("processing change for application %q, %v", w.application, err)
   142  			err = nil
   143  		}
   144  	}()
   145  
   146  	exposed, err := w.applicationGetter.IsExposed(w.application)
   147  	if err != nil {
   148  		return errors.Trace(err)
   149  	}
   150  	if !w.initial && exposed == w.previouslyExposed {
   151  		return nil
   152  	}
   153  
   154  	w.initial = false
   155  	w.previouslyExposed = exposed
   156  	if exposed {
   157  		appConfig, err := w.applicationGetter.ApplicationConfig(w.application)
   158  		if err != nil {
   159  			return errors.Trace(err)
   160  		}
   161  		resourceTags := tags.ResourceTags(
   162  			names.NewModelTag(w.modelUUID),
   163  			names.NewControllerTag(w.controllerUUID),
   164  		)
   165  		if err := w.serviceExposer.ExposeService(w.application, resourceTags, appConfig); err != nil {
   166  			return errors.Trace(err)
   167  		}
   168  		return nil
   169  	}
   170  	if err := w.serviceExposer.UnexposeService(w.application); err != nil {
   171  		return errors.Trace(err)
   172  	}
   173  	return nil
   174  }