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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package remoterelations
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names/v5"
    13  	"github.com/juju/worker/v3"
    14  	"github.com/juju/worker/v3/catacomb"
    15  	"gopkg.in/macaroon.v2"
    16  
    17  	"github.com/juju/juju/api/watcher"
    18  	"github.com/juju/juju/rpc/params"
    19  )
    20  
    21  // RelationUnitChangeEvent encapsulates a remote relation event,
    22  // adding the tag of the relation which changed.
    23  type RelationUnitChangeEvent struct {
    24  	Tag names.RelationTag
    25  	params.RemoteRelationChangeEvent
    26  }
    27  
    28  // relationUnitsWorker uses instances of watcher.RelationUnitsWatcher to
    29  // listen to changes to relation settings in a model, local or remote.
    30  // Local changes are exported to the remote model.
    31  // Remote changes are consumed by the local model.
    32  type relationUnitsWorker struct {
    33  	catacomb catacomb.Catacomb
    34  
    35  	mu sync.Mutex
    36  
    37  	// mostRecentChange is stored here for the engine report.
    38  	mostRecentChange RelationUnitChangeEvent
    39  	changeSince      time.Time
    40  
    41  	relationTag names.RelationTag
    42  	rrw         watcher.RemoteRelationWatcher
    43  	changes     chan<- RelationUnitChangeEvent
    44  	macaroon    *macaroon.Macaroon
    45  	mode        string // mode is local or remote.
    46  
    47  	logger Logger
    48  }
    49  
    50  func newRelationUnitsWorker(
    51  	relationTag names.RelationTag,
    52  	macaroon *macaroon.Macaroon,
    53  	rrw watcher.RemoteRelationWatcher,
    54  	changes chan<- RelationUnitChangeEvent,
    55  	logger Logger,
    56  	mode string,
    57  ) (*relationUnitsWorker, error) {
    58  	w := &relationUnitsWorker{
    59  		relationTag: relationTag,
    60  		macaroon:    macaroon,
    61  		rrw:         rrw,
    62  		changes:     changes,
    63  		logger:      logger,
    64  		mode:        mode,
    65  	}
    66  	err := catacomb.Invoke(catacomb.Plan{
    67  		Site: &w.catacomb,
    68  		Work: w.loop,
    69  		Init: []worker.Worker{rrw},
    70  	})
    71  	return w, err
    72  }
    73  
    74  // Kill is defined on worker.Worker
    75  func (w *relationUnitsWorker) Kill() {
    76  	w.catacomb.Kill(nil)
    77  }
    78  
    79  // Wait is defined on worker.Worker
    80  func (w *relationUnitsWorker) Wait() error {
    81  	err := w.catacomb.Wait()
    82  	if errors.IsNotFound(err) || params.IsCodeNotFound(err) {
    83  		err = nil
    84  	}
    85  	if err != nil {
    86  		w.logger.Errorf("error in relation units worker for %v: %v", w.relationTag.Id(), err)
    87  	}
    88  	return err
    89  }
    90  
    91  func (w *relationUnitsWorker) loop() error {
    92  	for {
    93  		select {
    94  		case <-w.catacomb.Dying():
    95  			return w.catacomb.ErrDying()
    96  		case change, ok := <-w.rrw.Changes():
    97  			if !ok {
    98  				// We are dying.
    99  				return w.catacomb.ErrDying()
   100  			}
   101  			w.logger.Debugf("%v relation units changed for %v: %#v", w.mode, w.relationTag, &change)
   102  			if isEmpty(change) {
   103  				continue
   104  			}
   105  
   106  			// Add macaroon in case this event is sent to a remote
   107  			// facade.
   108  
   109  			// TODO(babbageclunk): move this so it happens just before
   110  			// the event is published to the remote facade.
   111  			change.Macaroons = macaroon.Slice{w.macaroon}
   112  			change.BakeryVersion = bakery.LatestVersion
   113  
   114  			w.mu.Lock()
   115  			w.mostRecentChange = RelationUnitChangeEvent{
   116  				Tag:                       w.relationTag,
   117  				RemoteRelationChangeEvent: change,
   118  			}
   119  			w.changeSince = time.Now()
   120  			event := w.mostRecentChange
   121  			w.mu.Unlock()
   122  			// Send in lockstep so we don't drop events (otherwise
   123  			// we'd need to merge them - not too hard in this
   124  			// case but probably not needed).
   125  			select {
   126  			case <-w.catacomb.Dying():
   127  				return w.catacomb.ErrDying()
   128  			case w.changes <- event:
   129  			}
   130  		}
   131  	}
   132  }
   133  
   134  func isEmpty(change params.RemoteRelationChangeEvent) bool {
   135  	return len(change.ChangedUnits)+len(change.DepartedUnits) == 0 && change.ApplicationSettings == nil
   136  }
   137  
   138  // Report provides information for the engine report.
   139  func (w *relationUnitsWorker) Report() map[string]interface{} {
   140  	result := make(map[string]interface{})
   141  	w.mu.Lock()
   142  	defer w.mu.Unlock()
   143  
   144  	if w.mostRecentChange.Tag.Id() != "" {
   145  		var changed []int
   146  		for _, c := range w.mostRecentChange.ChangedUnits {
   147  			changed = append(changed, c.UnitId)
   148  		}
   149  		result["departed"] = w.mostRecentChange.DepartedUnits
   150  		result["changed"] = changed
   151  		result["since"] = w.changeSince.Format(time.RFC1123Z)
   152  	}
   153  
   154  	return result
   155  }