github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/remoterelations/remoteapplicationworker.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  	"fmt"
     8  	"sync"
     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"
    18  	"github.com/juju/juju/core/crossmodel"
    19  	"github.com/juju/juju/core/life"
    20  	"github.com/juju/juju/core/network"
    21  	"github.com/juju/juju/core/status"
    22  	"github.com/juju/juju/core/watcher"
    23  	"github.com/juju/juju/rpc/params"
    24  )
    25  
    26  // remoteApplicationWorker listens for localChanges to relations
    27  // involving a remote application, and publishes change to
    28  // local relation units to the remote model. It also watches for
    29  // changes originating from the offering model and consumes those
    30  // in the local model.
    31  type remoteApplicationWorker struct {
    32  	catacomb catacomb.Catacomb
    33  
    34  	mu sync.Mutex
    35  
    36  	// These attributes are relevant to dealing with a specific
    37  	// remote application proxy.
    38  	offerUUID                 string
    39  	applicationName           string // name of the remote application proxy in the local model
    40  	localModelUUID            string // uuid of the model hosting the local application
    41  	remoteModelUUID           string // uuid of the model hosting the remote offer
    42  	isConsumerProxy           bool
    43  	consumeVersion            int
    44  	localRelationUnitChanges  chan RelationUnitChangeEvent
    45  	remoteRelationUnitChanges chan RelationUnitChangeEvent
    46  	secretChangesWatcher      watcher.SecretsRevisionWatcher
    47  	secretChanges             watcher.SecretRevisionChannel
    48  
    49  	// relations is stored here for the engine report.
    50  	relations map[string]*relation
    51  
    52  	// offerMacaroon is used to confirm that permission has been granted to consume
    53  	// the remote application to which this worker pertains.
    54  	offerMacaroon *macaroon.Macaroon
    55  
    56  	// localModelFacade interacts with the local (consuming) model.
    57  	localModelFacade RemoteRelationsFacade
    58  	// remoteModelFacade interacts with the remote (offering) model.
    59  	remoteModelFacade RemoteModelRelationsFacadeCloser
    60  
    61  	newRemoteModelRelationsFacadeFunc newRemoteRelationsFacadeFunc
    62  
    63  	logger Logger
    64  }
    65  
    66  // relation holds attributes relevant to a particular
    67  // relation between a local app and a remote offer.
    68  type relation struct {
    69  	relationId     int
    70  	localDead      bool
    71  	suspended      bool
    72  	localUnitCount int
    73  	localRuw       *relationUnitsWorker
    74  	remoteRuw      *relationUnitsWorker
    75  	remoteRrw      *remoteRelationsWorker
    76  
    77  	applicationToken   string // token for app in local model
    78  	relationToken      string // token for relation in local model
    79  	localEndpoint      params.RemoteEndpoint
    80  	remoteEndpointName string
    81  	macaroon           *macaroon.Macaroon
    82  }
    83  
    84  // Kill is defined on worker.Worker
    85  func (w *remoteApplicationWorker) Kill() {
    86  	w.catacomb.Kill(nil)
    87  }
    88  
    89  // Wait is defined on worker.Worker
    90  func (w *remoteApplicationWorker) Wait() error {
    91  	err := w.catacomb.Wait()
    92  	if err != nil {
    93  		w.logger.Errorf("error in remote application worker for %v: %v", w.applicationName, err)
    94  	}
    95  	return err
    96  }
    97  
    98  func (w *remoteApplicationWorker) checkOfferPermissionDenied(err error, appToken, relationToken string) {
    99  	// If consume permission has been revoked for the offer, set the
   100  	// status of the local remote application entity.
   101  	if params.ErrCode(err) == params.CodeDischargeRequired {
   102  		if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, status.Error, err.Error()); err != nil {
   103  			w.logger.Errorf(
   104  				"updating remote application %v status from remote model %v: %v",
   105  				w.applicationName, w.remoteModelUUID, err)
   106  		}
   107  		w.logger.Debugf("discharge required error: app token: %v rel token: %v", appToken, relationToken)
   108  		// If we know a specific relation, update that too.
   109  		if relationToken != "" {
   110  			suspended := true
   111  			event := params.RemoteRelationChangeEvent{
   112  				RelationToken:    relationToken,
   113  				ApplicationToken: appToken,
   114  				Suspended:        &suspended,
   115  				SuspendedReason:  "offer permission revoked",
   116  			}
   117  			if err := w.localModelFacade.ConsumeRemoteRelationChange(event); err != nil {
   118  				w.logger.Errorf("updating relation status: %v", err)
   119  			}
   120  		}
   121  	}
   122  }
   123  
   124  func (w *remoteApplicationWorker) remoteOfferRemoved() error {
   125  	w.logger.Debugf("remote offer for %s has been removed", w.applicationName)
   126  	if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, status.Terminated, "offer has been removed"); err != nil {
   127  		return errors.Annotatef(err, "updating remote application %v status from remote model %v", w.applicationName, w.remoteModelUUID)
   128  	}
   129  	return nil
   130  }
   131  
   132  // isNotFound allows either type of not found error
   133  // to be correctly handled.
   134  // TODO(wallyworld) - remove when all api facades are fixed
   135  func isNotFound(err error) bool {
   136  	if err == nil {
   137  		return false
   138  	}
   139  	return errors.IsNotFound(err) || params.IsCodeNotFound(err)
   140  }
   141  
   142  func (w *remoteApplicationWorker) loop() (err error) {
   143  	// Watch for changes to any local relations to the remote application.
   144  	relationsWatcher, err := w.localModelFacade.WatchRemoteApplicationRelations(w.applicationName)
   145  	if err != nil && isNotFound(err) {
   146  		return nil
   147  	} else if err != nil {
   148  		return errors.Annotatef(err, "watching relations for remote application %q", w.applicationName)
   149  	}
   150  	if err := w.catacomb.Add(relationsWatcher); err != nil {
   151  		return errors.Trace(err)
   152  	}
   153  
   154  	// On the consuming side, watch for status changes to the offer.
   155  	var (
   156  		offerStatusWatcher watcher.OfferStatusWatcher
   157  		offerStatusChanges watcher.OfferStatusChannel
   158  	)
   159  	if !w.isConsumerProxy {
   160  		if err := w.newRemoteRelationsFacadeWithRedirect(); err != nil {
   161  			msg := fmt.Sprintf("cannot connect to external controller: %v", err.Error())
   162  			if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, status.Error, msg); err != nil {
   163  				return errors.Annotatef(err, "updating remote application %v status from remote model %v", w.applicationName, w.remoteModelUUID)
   164  			}
   165  			return errors.Annotate(err, "cannot connect to external controller")
   166  		}
   167  		defer func() {
   168  			if err := w.remoteModelFacade.Close(); err != nil {
   169  				w.logger.Errorf("error closing remote-relations facade: %s", err)
   170  			}
   171  		}()
   172  
   173  		arg := params.OfferArg{
   174  			OfferUUID: w.offerUUID,
   175  		}
   176  		if w.offerMacaroon != nil {
   177  			arg.Macaroons = macaroon.Slice{w.offerMacaroon}
   178  			arg.BakeryVersion = bakery.LatestVersion
   179  		}
   180  
   181  		offerStatusWatcher, err = w.remoteModelFacade.WatchOfferStatus(arg)
   182  		if err != nil {
   183  			w.checkOfferPermissionDenied(err, "", "")
   184  			if isNotFound(err) {
   185  				return w.remoteOfferRemoved()
   186  			}
   187  			return errors.Annotate(err, "watching status for offer")
   188  		}
   189  		if err := w.catacomb.Add(offerStatusWatcher); err != nil {
   190  			return errors.Trace(err)
   191  		}
   192  		offerStatusChanges = offerStatusWatcher.Changes()
   193  	}
   194  
   195  	w.mu.Lock()
   196  	w.relations = make(map[string]*relation)
   197  	w.mu.Unlock()
   198  	for {
   199  		select {
   200  		case <-w.catacomb.Dying():
   201  			return w.catacomb.ErrDying()
   202  		case change, ok := <-relationsWatcher.Changes():
   203  			w.logger.Debugf("relations changed: %#v, %v", &change, ok)
   204  			if !ok {
   205  				// We are dying.
   206  				return w.catacomb.ErrDying()
   207  			}
   208  			results, err := w.localModelFacade.Relations(change)
   209  			if err != nil {
   210  				return errors.Annotate(err, "querying relations")
   211  			}
   212  			for i, result := range results {
   213  				key := change[i]
   214  				if err := w.relationChanged(key, result); err != nil {
   215  					if isNotFound(err) {
   216  						// Relation has been deleted, so ensure relevant workers are stopped.
   217  						w.logger.Debugf("relation %q changed but has been removed", key)
   218  						err2 := w.localRelationChanged(key, nil)
   219  						if err2 != nil {
   220  							return errors.Annotatef(err2, "cleaning up removed local relation %q", key)
   221  						}
   222  						continue
   223  					}
   224  					return errors.Annotatef(err, "handling change for relation %q", key)
   225  				}
   226  			}
   227  		case change := <-w.localRelationUnitChanges:
   228  			w.logger.Debugf("local relation units changed -> publishing: %#v", &change)
   229  			// TODO(babbageclunk): add macaroons to event here instead
   230  			// of in the relation units worker.
   231  			if err := w.remoteModelFacade.PublishRelationChange(change.RemoteRelationChangeEvent); err != nil {
   232  				w.checkOfferPermissionDenied(err, change.ApplicationToken, change.RelationToken)
   233  				if isNotFound(err) || params.IsCodeCannotEnterScope(err) {
   234  					w.logger.Debugf("relation %v changed but remote side already removed", change.Tag.Id())
   235  					continue
   236  				}
   237  				return errors.Annotatef(err, "publishing relation change %#v to remote model %v", &change, w.remoteModelUUID)
   238  			}
   239  
   240  			// TODO(juju4) - remove
   241  			// UnitCount has had omitempty removed, but we need to account for older controllers.
   242  			zero := 0
   243  			unitCount := change.UnitCount
   244  			if unitCount == nil {
   245  				unitCount = &zero
   246  			}
   247  
   248  			if err := w.localRelationChanged(change.Tag.Id(), unitCount); err != nil {
   249  				return errors.Annotatef(err, "processing local relation change for %v", change.Tag.Id())
   250  			}
   251  		case change := <-w.remoteRelationUnitChanges:
   252  			w.logger.Debugf("remote relation units changed -> consuming: %#v", &change)
   253  			if err := w.localModelFacade.ConsumeRemoteRelationChange(change.RemoteRelationChangeEvent); err != nil {
   254  				if isNotFound(err) || params.IsCodeCannotEnterScope(err) {
   255  					w.logger.Debugf("relation %v changed but local side already removed", change.Tag.Id())
   256  					continue
   257  				}
   258  				return errors.Annotatef(err, "consuming relation change %#v from remote model %v", &change, w.remoteModelUUID)
   259  			}
   260  		case changes := <-offerStatusChanges:
   261  			w.logger.Debugf("offer status changed: %#v", changes)
   262  			for _, change := range changes {
   263  				if err := w.localModelFacade.SetRemoteApplicationStatus(w.applicationName, change.Status.Status, change.Status.Message); err != nil {
   264  					return errors.Annotatef(err, "updating remote application %v status from remote model %v", w.applicationName, w.remoteModelUUID)
   265  				}
   266  				// If the offer is terminated the status watcher can be stopped immediately.
   267  				if change.Status.Status == status.Terminated {
   268  					offerStatusWatcher.Kill()
   269  					if err := offerStatusWatcher.Wait(); err != nil {
   270  						w.logger.Warningf("error stopping status watcher for saas application %s: %v", w.applicationName, err)
   271  					}
   272  					offerStatusChanges = nil
   273  					break
   274  				}
   275  			}
   276  		case changes := <-w.secretChanges:
   277  			err := w.localModelFacade.ConsumeRemoteSecretChanges(changes)
   278  			if err != nil {
   279  				if isNotFound(err) {
   280  					w.logger.Debugf("secrets %v changed but local side already removed", changes)
   281  					continue
   282  				}
   283  				return errors.Annotatef(err, "consuming secrets change %#v from remote model %v", changes, w.remoteModelUUID)
   284  			}
   285  		}
   286  	}
   287  }
   288  
   289  // newRemoteRelationsFacadeWithRedirect attempts to open an API connection to
   290  // the remote model for the watcher's application.
   291  // If a redirect error is returned, we attempt to open a connection to the new
   292  // controller and update our local controller info for the model so that future
   293  // API connections are to the new location.
   294  func (w *remoteApplicationWorker) newRemoteRelationsFacadeWithRedirect() error {
   295  	apiInfo, err := w.localModelFacade.ControllerAPIInfoForModel(w.remoteModelUUID)
   296  	if err != nil {
   297  		return errors.Annotate(err, "cannot get controller api info for remote model")
   298  	}
   299  	w.logger.Debugf("remote controller API addresses: %v", apiInfo.Addrs)
   300  
   301  	w.remoteModelFacade, err = w.newRemoteModelRelationsFacadeFunc(apiInfo)
   302  	if redirectErr, ok := errors.Cause(err).(*api.RedirectError); ok {
   303  		apiInfo.Addrs = network.CollapseToHostPorts(redirectErr.Servers).Strings()
   304  		apiInfo.CACert = redirectErr.CACert
   305  
   306  		w.logger.Debugf("received redirect; new API addresses: %v", apiInfo.Addrs)
   307  
   308  		if w.remoteModelFacade, err = w.newRemoteModelRelationsFacadeFunc(apiInfo); err == nil {
   309  			// We successfully followed the redirect,
   310  			// so update the controller information for this model.
   311  			controllerInfo := crossmodel.ControllerInfo{
   312  				ControllerTag: redirectErr.ControllerTag,
   313  				Alias:         redirectErr.ControllerAlias,
   314  				Addrs:         apiInfo.Addrs,
   315  				CACert:        apiInfo.CACert,
   316  			}
   317  
   318  			if err = w.localModelFacade.UpdateControllerForModel(controllerInfo, w.remoteModelUUID); err != nil {
   319  				_ = w.remoteModelFacade.Close()
   320  				err = errors.Annotate(err, "updating external controller info")
   321  			}
   322  		}
   323  	}
   324  
   325  	return errors.Annotate(err, "opening facade to remote model")
   326  }
   327  
   328  func (w *remoteApplicationWorker) processRelationDying(key string, r *relation, forceCleanup bool) error {
   329  	w.logger.Debugf("relation %v dying (%v)", key, forceCleanup)
   330  	// On the consuming side, inform the remote side the relation is dying
   331  	// (but only if we are killing the relation due to it dying, not because
   332  	// it is suspended).
   333  	if !w.isConsumerProxy {
   334  		change := params.RemoteRelationChangeEvent{
   335  			RelationToken:    r.relationToken,
   336  			Life:             life.Dying,
   337  			ApplicationToken: r.applicationToken,
   338  			Macaroons:        macaroon.Slice{r.macaroon},
   339  			BakeryVersion:    bakery.LatestVersion,
   340  		}
   341  		// forceCleanup will be true if the worker has restarted and because the relation had
   342  		// already been removed, we won't get any more unit departed events.
   343  		if forceCleanup {
   344  			change.ForceCleanup = &forceCleanup
   345  		}
   346  		if err := w.remoteModelFacade.PublishRelationChange(change); err != nil {
   347  			w.checkOfferPermissionDenied(err, r.applicationToken, r.relationToken)
   348  			if isNotFound(err) {
   349  				w.logger.Debugf("relation %v dying but remote side already removed", key)
   350  				return nil
   351  			}
   352  			return errors.Annotatef(err, "publishing relation dying %#v to remote model %v", &change, w.remoteModelUUID)
   353  		}
   354  	}
   355  	return nil
   356  }
   357  
   358  func (w *remoteApplicationWorker) processRelationSuspended(key string, relLife life.Value, relations map[string]*relation) error {
   359  	w.logger.Debugf("(%v) relation %v suspended", relLife, key)
   360  	relation, ok := relations[key]
   361  	if !ok {
   362  		return nil
   363  	}
   364  
   365  	// Only stop the watchers for relation unit changes if relation is alive,
   366  	// as we need to always deal with units leaving scope etc if the relation is dying.
   367  	if relLife != life.Alive {
   368  		return nil
   369  	}
   370  
   371  	// On the offering side, if the relation is resumed,
   372  	// it will be treated like the relation has been joined
   373  	// for the first time; all workers will be restarted.
   374  	// The offering side has isConsumerProxy = true.
   375  	if w.isConsumerProxy {
   376  		delete(relations, key)
   377  	}
   378  
   379  	if relation.localRuw != nil {
   380  		if err := worker.Stop(relation.localRuw); err != nil {
   381  			w.logger.Warningf("stopping local relation unit worker for %v: %v", key, err)
   382  		}
   383  		relation.localRuw = nil
   384  	}
   385  	if relation.remoteRuw != nil {
   386  		if err := worker.Stop(relation.remoteRuw); err != nil {
   387  			w.logger.Warningf("stopping remote relation unit worker for %v: %v", key, err)
   388  		}
   389  		relation.remoteRuw = nil
   390  	}
   391  	return nil
   392  }
   393  
   394  // processLocalRelationRemoved is called when a change event arrives from the remote model
   395  // but the relation in the local model has been removed.
   396  func (w *remoteApplicationWorker) processLocalRelationRemoved(key string, relations map[string]*relation) error {
   397  	w.logger.Debugf("local relation %v removed", key)
   398  	relation, ok := relations[key]
   399  	if !ok {
   400  		return nil
   401  	}
   402  
   403  	// Stop the worker which watches remote status/life.
   404  	if relation.remoteRrw != nil {
   405  		if err := worker.Stop(relation.remoteRrw); err != nil {
   406  			w.logger.Warningf("stopping remote relations worker for %v: %v", key, err)
   407  		}
   408  		relation.remoteRrw = nil
   409  		relations[key] = relation
   410  	}
   411  
   412  	w.logger.Debugf("remote relation %v removed from local model", key)
   413  	return nil
   414  }
   415  
   416  // localRelationChanged processes changes to the relation
   417  // as recorded in the local model; the primary function
   418  // is to shut down workers when the relation is dead.
   419  func (w *remoteApplicationWorker) localRelationChanged(key string, unitCountPtr *int) error {
   420  	unitCountMsg := " (removed)"
   421  	if unitCountPtr != nil {
   422  		unitCountMsg = fmt.Sprintf(", still has %d unit(s) in scope", *unitCountPtr)
   423  	}
   424  	w.logger.Debugf("local relation %v changed%s", key, unitCountMsg)
   425  	w.mu.Lock()
   426  	defer w.mu.Unlock()
   427  
   428  	relation, ok := w.relations[key]
   429  	if !ok {
   430  		w.logger.Debugf("local relation %v already gone", key)
   431  		return nil
   432  	}
   433  	w.logger.Debugf("relation %v in mem unit count is %d", key, relation.localUnitCount)
   434  	if unitCountPtr != nil {
   435  		relation.localUnitCount = *unitCountPtr
   436  	}
   437  	if !relation.localDead {
   438  		w.logger.Debugf("local relation %v not dead yet", key)
   439  		return nil
   440  	}
   441  	if relation.localUnitCount > 0 {
   442  		w.logger.Debugf("relation dead but still has %d units in scope", relation.localUnitCount)
   443  		return nil
   444  	}
   445  	return w.terminateLocalRelation(key)
   446  }
   447  
   448  func (w *remoteApplicationWorker) terminateLocalRelation(key string) error {
   449  	relation, ok := w.relations[key]
   450  	if !ok {
   451  		return nil
   452  	}
   453  	delete(w.relations, key)
   454  	w.logger.Debugf("local relation %v is terminated", key)
   455  
   456  	// For the unit watchers, check to see if these are nil before stopping.
   457  	// They will be nil if the relation was suspended and then we kill it for real.
   458  	if relation.localRuw != nil {
   459  		if err := worker.Stop(relation.localRuw); err != nil {
   460  			w.logger.Warningf("stopping local relation unit worker for %v: %v", key, err)
   461  		}
   462  		relation.localRuw = nil
   463  	}
   464  	if relation.remoteRuw != nil {
   465  		if err := worker.Stop(relation.remoteRuw); err != nil {
   466  			w.logger.Warningf("stopping remote relation unit worker for %v: %v", key, err)
   467  		}
   468  		relation.remoteRuw = nil
   469  	}
   470  
   471  	w.logger.Debugf("local relation %v removed from local model", key)
   472  	return nil
   473  }
   474  
   475  // relationChanged processes changes to the relation as recorded in the
   476  // local model when a change event arrives from the remote model.
   477  func (w *remoteApplicationWorker) relationChanged(key string, localRelation params.RemoteRelationResult) (err error) {
   478  	w.logger.Debugf("relation %q changed in local model: %#v", key, localRelation)
   479  	w.mu.Lock()
   480  	defer w.mu.Unlock()
   481  
   482  	defer func() {
   483  		if err == nil || !isNotFound(err) {
   484  			return
   485  		}
   486  		if err2 := w.processLocalRelationRemoved(key, w.relations); err2 != nil {
   487  			err = errors.Annotate(err2, "processing local relation removed")
   488  		}
   489  		if r := w.relations[key]; r != nil {
   490  			r.localDead = true
   491  			w.relations[key] = r
   492  		}
   493  	}()
   494  	if localRelation.Error != nil {
   495  		return localRelation.Error
   496  	}
   497  	localRelationInfo := localRelation.Result
   498  
   499  	// If we have previously started the watcher and the
   500  	// relation is now suspended, stop the watcher.
   501  	if r := w.relations[key]; r != nil {
   502  		wasSuspended := r.suspended
   503  		r.suspended = localRelationInfo.Suspended
   504  		w.relations[key] = r
   505  		if localRelationInfo.Suspended {
   506  			return w.processRelationSuspended(key, localRelationInfo.Life, w.relations)
   507  		}
   508  		if localRelationInfo.Life == life.Alive {
   509  			if r.localDead {
   510  				// A previous relation with the same name was removed but
   511  				// not cleaned up properly so do it now before starting up
   512  				// workers again.
   513  				w.logger.Debugf("still have zombie local relation %v", key)
   514  				if err := w.terminateLocalRelation(key); err != nil {
   515  					return errors.Annotatef(err, "terminating zombie local relation %v", key)
   516  				}
   517  			} else if !wasSuspended {
   518  				// Nothing to do, we have previously started the watcher.
   519  				return nil
   520  			}
   521  		}
   522  	}
   523  
   524  	if w.isConsumerProxy {
   525  		// Nothing else to do on the offering side.
   526  		return nil
   527  	}
   528  	return w.processConsumingRelation(key, localRelationInfo)
   529  }
   530  
   531  // startUnitsWorkers starts 2 workers to watch for unit settings or departed changes;
   532  // one worker is for the local model, the other for the remote model.
   533  func (w *remoteApplicationWorker) startUnitsWorkers(
   534  	relationTag names.RelationTag,
   535  	relationToken, remoteAppToken string,
   536  	applicationName string,
   537  	mac *macaroon.Macaroon,
   538  ) (*relationUnitsWorker, *relationUnitsWorker, error) {
   539  	// Start a watcher to track changes to the units in the relation in the local model.
   540  	localRelationUnitsWatcher, err := w.localModelFacade.WatchLocalRelationChanges(relationTag.Id())
   541  	if err != nil {
   542  		return nil, nil, errors.Annotatef(err, "watching local side of relation %v", relationTag.Id())
   543  	}
   544  
   545  	localUnitsWorker, err := newRelationUnitsWorker(
   546  		relationTag,
   547  		mac,
   548  		localRelationUnitsWatcher,
   549  		w.localRelationUnitChanges,
   550  		w.logger,
   551  		"local",
   552  	)
   553  	if err != nil {
   554  		return nil, nil, errors.Trace(err)
   555  	}
   556  	if err := w.catacomb.Add(localUnitsWorker); err != nil {
   557  		return nil, nil, errors.Trace(err)
   558  	}
   559  
   560  	// Start a watcher to track changes to the units in the relation in the remote model.
   561  	remoteRelationUnitsWatcher, err := w.remoteModelFacade.WatchRelationChanges(
   562  		relationToken, remoteAppToken, macaroon.Slice{mac},
   563  	)
   564  	if err != nil {
   565  		w.checkOfferPermissionDenied(err, remoteAppToken, relationToken)
   566  		return nil, nil, errors.Annotatef(
   567  			err, "watching remote side of application %v and relation %v",
   568  			applicationName, relationTag.Id())
   569  	}
   570  
   571  	remoteUnitsWorker, err := newRelationUnitsWorker(
   572  		relationTag,
   573  		mac,
   574  		remoteRelationUnitsWatcher,
   575  		w.remoteRelationUnitChanges,
   576  		w.logger,
   577  		"remote",
   578  	)
   579  	if err != nil {
   580  		return nil, nil, errors.Trace(err)
   581  	}
   582  	if err := w.catacomb.Add(remoteUnitsWorker); err != nil {
   583  		return nil, nil, errors.Trace(err)
   584  	}
   585  	return localUnitsWorker, remoteUnitsWorker, nil
   586  }
   587  
   588  // processConsumingRelation starts the sub-workers necessary to listen and publish
   589  // local unit settings changes, and watch and consume remote unit settings changes.
   590  // Ths will be called when a new relation is created or when a relation resumes
   591  // after being suspended.
   592  func (w *remoteApplicationWorker) processConsumingRelation(
   593  	key string,
   594  	remoteRelation *params.RemoteRelation,
   595  ) error {
   596  
   597  	// We have not seen the relation before, make
   598  	// sure it is registered on the offering side.
   599  	// Or relation was suspended and is now resumed so re-register.
   600  	applicationTag := names.NewApplicationTag(remoteRelation.ApplicationName)
   601  	relationTag := names.NewRelationTag(key)
   602  	applicationToken, remoteAppToken, relationToken, mac, err := w.registerRemoteRelation(
   603  		applicationTag, relationTag, w.offerUUID, w.consumeVersion,
   604  		remoteRelation.Endpoint, remoteRelation.RemoteEndpointName)
   605  	if err != nil {
   606  		w.checkOfferPermissionDenied(err, "", "")
   607  		return errors.Annotatef(err, "registering application %v and relation %v", remoteRelation.ApplicationName, relationTag.Id())
   608  	}
   609  	w.logger.Debugf("remote relation registered for %q: app token=%q, rel token=%q, remote app token=%q", key, applicationToken, relationToken, remoteAppToken)
   610  
   611  	// Have we seen the relation before.
   612  	r, relationKnown := w.relations[key]
   613  	if !relationKnown {
   614  		// Totally new so start the lifecycle watcher.
   615  		remoteRelationsWatcher, err := w.remoteModelFacade.WatchRelationSuspendedStatus(params.RemoteEntityArg{
   616  			Token:         relationToken,
   617  			Macaroons:     macaroon.Slice{mac},
   618  			BakeryVersion: bakery.LatestVersion,
   619  		})
   620  		if err != nil {
   621  			w.checkOfferPermissionDenied(err, remoteAppToken, relationToken)
   622  			return errors.Annotatef(err, "watching remote side of relation %v", remoteRelation.Key)
   623  		}
   624  
   625  		remoteRelationsWorker, err := newRemoteRelationsWorker(
   626  			relationTag,
   627  			remoteAppToken,
   628  			relationToken,
   629  			remoteRelationsWatcher,
   630  			w.remoteRelationUnitChanges,
   631  			w.logger,
   632  		)
   633  		if err != nil {
   634  			return errors.Trace(err)
   635  		}
   636  		if err := w.catacomb.Add(remoteRelationsWorker); err != nil {
   637  			return errors.Trace(err)
   638  		}
   639  		r = &relation{
   640  			relationId:         remoteRelation.Id,
   641  			suspended:          remoteRelation.Suspended,
   642  			localUnitCount:     remoteRelation.UnitCount,
   643  			remoteRrw:          remoteRelationsWorker,
   644  			macaroon:           mac,
   645  			localEndpoint:      remoteRelation.Endpoint,
   646  			remoteEndpointName: remoteRelation.RemoteEndpointName,
   647  			applicationToken:   applicationToken,
   648  			relationToken:      relationToken,
   649  		}
   650  		w.relations[key] = r
   651  	}
   652  
   653  	if r.localRuw == nil && !remoteRelation.Suspended {
   654  		// Also start the units watchers (local and remote).
   655  		localUnitsWorker, remoteUnitsWorker, err := w.startUnitsWorkers(
   656  			relationTag, relationToken, remoteAppToken, remoteRelation.ApplicationName, mac)
   657  		if err != nil {
   658  			return errors.Annotate(err, "starting relation units workers")
   659  		}
   660  		r.localRuw = localUnitsWorker
   661  		r.remoteRuw = remoteUnitsWorker
   662  	}
   663  
   664  	if w.secretChangesWatcher == nil {
   665  		w.secretChangesWatcher, err = w.remoteModelFacade.WatchConsumedSecretsChanges(applicationToken, relationToken, w.offerMacaroon)
   666  		if err != nil && !errors.Is(err, errors.NotFound) && !errors.Is(err, errors.NotImplemented) {
   667  			w.checkOfferPermissionDenied(err, "", "")
   668  			return errors.Annotate(err, "watching consumed secret changes")
   669  		}
   670  		if err == nil {
   671  			if err := w.catacomb.Add(w.secretChangesWatcher); err != nil {
   672  				return errors.Trace(err)
   673  			}
   674  			w.secretChanges = w.secretChangesWatcher.Changes()
   675  		}
   676  	}
   677  
   678  	// If the relation is dying, stop the watcher.
   679  	if remoteRelation.Life != life.Alive {
   680  		return w.processRelationDying(key, r, !relationKnown)
   681  	}
   682  
   683  	return nil
   684  }
   685  
   686  func (w *remoteApplicationWorker) registerRemoteRelation(
   687  	applicationTag, relationTag names.Tag, offerUUID string, consumeVersion int,
   688  	localEndpointInfo params.RemoteEndpoint, remoteEndpointName string,
   689  ) (applicationToken, offeringAppToken, relationToken string, _ *macaroon.Macaroon, _ error) {
   690  	w.logger.Debugf("register remote relation %v to local application %v", relationTag.Id(), applicationTag.Id())
   691  
   692  	fail := func(err error) (string, string, string, *macaroon.Macaroon, error) {
   693  		return "", "", "", nil, err
   694  	}
   695  
   696  	// Ensure the relation is exported first up.
   697  	results, err := w.localModelFacade.ExportEntities([]names.Tag{applicationTag, relationTag})
   698  	if err != nil {
   699  		return fail(errors.Annotatef(err, "exporting relation %v and application %v", relationTag, applicationTag))
   700  	}
   701  	if results[0].Error != nil && !params.IsCodeAlreadyExists(results[0].Error) {
   702  		return fail(errors.Annotatef(err, "exporting application %v", applicationTag))
   703  	}
   704  	applicationToken = results[0].Token
   705  	if results[1].Error != nil && !params.IsCodeAlreadyExists(results[1].Error) {
   706  		return fail(errors.Annotatef(err, "exporting relation %v", relationTag))
   707  	}
   708  	relationToken = results[1].Token
   709  
   710  	// This data goes to the remote model so we map local info
   711  	// from this model to the remote arg values and visa versa.
   712  	arg := params.RegisterRemoteRelationArg{
   713  		ApplicationToken:  applicationToken,
   714  		SourceModelTag:    names.NewModelTag(w.localModelUUID).String(),
   715  		RelationToken:     relationToken,
   716  		OfferUUID:         offerUUID,
   717  		RemoteEndpoint:    localEndpointInfo,
   718  		LocalEndpointName: remoteEndpointName,
   719  		ConsumeVersion:    consumeVersion,
   720  	}
   721  	if w.offerMacaroon != nil {
   722  		arg.Macaroons = macaroon.Slice{w.offerMacaroon}
   723  		arg.BakeryVersion = bakery.LatestVersion
   724  	}
   725  	remoteRelation, err := w.remoteModelFacade.RegisterRemoteRelations(arg)
   726  	if err != nil {
   727  		return fail(errors.Trace(err))
   728  	}
   729  	// remoteAppIds is a slice but there's only one item
   730  	// as we currently only register one remote application
   731  	if err := remoteRelation[0].Error; err != nil {
   732  		return fail(errors.Annotatef(err, "registering relation %v", relationTag))
   733  	}
   734  	// Import the application id from the offering model.
   735  	registerResult := *remoteRelation[0].Result
   736  	offeringAppToken = registerResult.Token
   737  	// We have a new macaroon attenuated to the relation.
   738  	// Save for the firewaller.
   739  	if err := w.localModelFacade.SaveMacaroon(relationTag, registerResult.Macaroon); err != nil {
   740  		return fail(errors.Annotatef(
   741  			err, "saving macaroon for %v", relationTag))
   742  	}
   743  
   744  	appTag := names.NewApplicationTag(w.applicationName)
   745  	w.logger.Debugf("import remote application token %v for %v", offeringAppToken, w.applicationName)
   746  	err = w.localModelFacade.ImportRemoteEntity(appTag, offeringAppToken)
   747  	if err != nil && !params.IsCodeAlreadyExists(err) {
   748  		return fail(errors.Annotatef(
   749  			err, "importing remote application %v to local model", w.applicationName))
   750  	}
   751  	return applicationToken, offeringAppToken, relationToken, registerResult.Macaroon, nil
   752  }
   753  
   754  // Report provides information for the engine report.
   755  func (w *remoteApplicationWorker) Report() map[string]interface{} {
   756  	result := make(map[string]interface{})
   757  	w.mu.Lock()
   758  	defer w.mu.Unlock()
   759  
   760  	relationsInfo := make(map[string]interface{})
   761  	for rel, info := range w.relations {
   762  		report := map[string]interface{}{
   763  			"relation-id":       info.relationId,
   764  			"local-dead":        info.localDead,
   765  			"suspended":         info.suspended,
   766  			"application-token": info.applicationToken,
   767  			"relation-token":    info.relationToken,
   768  			"local-endpoint":    info.localEndpoint.Name,
   769  			"remote-endpoint":   info.remoteEndpointName,
   770  		}
   771  		if info.remoteRrw != nil {
   772  			report["last-status-event"] = info.remoteRrw.Report()
   773  		}
   774  		if info.localRuw != nil {
   775  			report["last-local-change"] = info.localRuw.Report()
   776  		}
   777  		if info.remoteRuw != nil {
   778  			report["last-remote-change"] = info.remoteRuw.Report()
   779  		}
   780  		relationsInfo[rel] = report
   781  	}
   782  	if len(relationsInfo) > 0 {
   783  		result["relations"] = relationsInfo
   784  	}
   785  	result["remote-model-uuid"] = w.remoteModelUUID
   786  	if w.isConsumerProxy {
   787  		result["consumer-proxy"] = true
   788  		result["consume-version"] = w.consumeVersion
   789  	} else {
   790  		result["saas-application"] = true
   791  		result["offer-uuid"] = w.offerUUID
   792  	}
   793  
   794  	return result
   795  }