github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/remoterelations/remoterelations.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package remoterelations
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/juju/clock"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/names/v5"
    16  	"github.com/juju/worker/v3"
    17  	"github.com/juju/worker/v3/catacomb"
    18  	"gopkg.in/macaroon.v2"
    19  
    20  	"github.com/juju/juju/api"
    21  	apiwatcher "github.com/juju/juju/api/watcher"
    22  	"github.com/juju/juju/core/crossmodel"
    23  	"github.com/juju/juju/core/life"
    24  	"github.com/juju/juju/core/status"
    25  	"github.com/juju/juju/core/watcher"
    26  	"github.com/juju/juju/rpc/params"
    27  )
    28  
    29  // logger is here to stop the desire of creating a package level logger.
    30  // Don't do this, instead use the one passed as manifold config.
    31  type logger interface{}
    32  
    33  var _ logger = struct{}{}
    34  
    35  // RemoteModelRelationsFacadeCloser implements RemoteModelRelationsFacade
    36  // and add a Close() method.
    37  type RemoteModelRelationsFacadeCloser interface {
    38  	io.Closer
    39  	RemoteModelRelationsFacade
    40  }
    41  
    42  // RemoteModelRelationsFacade instances publish local relation changes to the
    43  // model hosting the remote application involved in the relation, and also watches
    44  // for remote relation changes which are then pushed to the local model.
    45  type RemoteModelRelationsFacade interface {
    46  	// RegisterRemoteRelations sets up the remote model to participate
    47  	// in the specified relations.
    48  	RegisterRemoteRelations(relations ...params.RegisterRemoteRelationArg) ([]params.RegisterRemoteRelationResult, error)
    49  
    50  	// PublishRelationChange publishes relation changes to the
    51  	// model hosting the remote application involved in the relation.
    52  	PublishRelationChange(params.RemoteRelationChangeEvent) error
    53  
    54  	// WatchRelationChanges returns a watcher that notifies of changes
    55  	// to the units in the remote model for the relation with the
    56  	// given remote token. We need to pass the application token for
    57  	// the case where we're talking to a v1 API and the client needs
    58  	// to convert RelationUnitsChanges into RemoteRelationChangeEvents
    59  	// as they come in.
    60  	WatchRelationChanges(relationToken, applicationToken string, macs macaroon.Slice) (apiwatcher.RemoteRelationWatcher, error)
    61  
    62  	// WatchRelationSuspendedStatus starts a RelationStatusWatcher for watching the
    63  	// relations of each specified application in the remote model.
    64  	WatchRelationSuspendedStatus(arg params.RemoteEntityArg) (watcher.RelationStatusWatcher, error)
    65  
    66  	// WatchOfferStatus starts an OfferStatusWatcher for watching the status
    67  	// of the specified offer in the remote model.
    68  	WatchOfferStatus(arg params.OfferArg) (watcher.OfferStatusWatcher, error)
    69  
    70  	// WatchConsumedSecretsChanges starts a watcher for any changes to secrets
    71  	// consumed by the specified application.
    72  	WatchConsumedSecretsChanges(applicationToken, relationToken string, mac *macaroon.Macaroon) (watcher.SecretsRevisionWatcher, error)
    73  }
    74  
    75  // RemoteRelationsFacade exposes remote relation functionality to a worker.
    76  type RemoteRelationsFacade interface {
    77  	// ImportRemoteEntity adds an entity to the remote entities collection
    78  	// with the specified opaque token.
    79  	ImportRemoteEntity(entity names.Tag, token string) error
    80  
    81  	// SaveMacaroon saves the macaroon for the entity.
    82  	SaveMacaroon(entity names.Tag, mac *macaroon.Macaroon) error
    83  
    84  	// ExportEntities allocates unique, remote entity IDs for the
    85  	// given entities in the local model.
    86  	ExportEntities([]names.Tag) ([]params.TokenResult, error)
    87  
    88  	// GetToken returns the token associated with the entity with the given tag.
    89  	GetToken(names.Tag) (string, error)
    90  
    91  	// Relations returns information about the relations
    92  	// with the specified keys in the local model.
    93  	Relations(keys []string) ([]params.RemoteRelationResult, error)
    94  
    95  	// RemoteApplications returns the current state of the remote applications with
    96  	// the specified names in the local model.
    97  	RemoteApplications(names []string) ([]params.RemoteApplicationResult, error)
    98  
    99  	// WatchLocalRelationChanges returns a watcher that notifies of changes to the
   100  	// local units in the relation with the given key.
   101  	WatchLocalRelationChanges(relationKey string) (apiwatcher.RemoteRelationWatcher, error)
   102  
   103  	// WatchRemoteApplications watches for addition, removal and lifecycle
   104  	// changes to remote applications known to the local model.
   105  	WatchRemoteApplications() (watcher.StringsWatcher, error)
   106  
   107  	// WatchRemoteApplicationRelations starts a StringsWatcher for watching the relations of
   108  	// each specified application in the local model, and returns the watcher IDs
   109  	// and initial values, or an error if the application's relations could not be
   110  	// watched.
   111  	WatchRemoteApplicationRelations(application string) (watcher.StringsWatcher, error)
   112  
   113  	// ConsumeRemoteRelationChange consumes a change to settings originating
   114  	// from the remote/offering side of a relation.
   115  	ConsumeRemoteRelationChange(change params.RemoteRelationChangeEvent) error
   116  
   117  	// ControllerAPIInfoForModel returns the controller api info for a model.
   118  	ControllerAPIInfoForModel(modelUUID string) (*api.Info, error)
   119  
   120  	// SetRemoteApplicationStatus sets the status for the specified remote application.
   121  	SetRemoteApplicationStatus(applicationName string, status status.Status, message string) error
   122  
   123  	// UpdateControllerForModel ensures that there is an external controller record
   124  	// for the input info, associated with the input model ID.
   125  	UpdateControllerForModel(controller crossmodel.ControllerInfo, modelUUID string) error
   126  
   127  	// ConsumeRemoteSecretChanges updates the local model with secret revision  changes
   128  	// originating from the remote/offering model.
   129  	ConsumeRemoteSecretChanges(changes []watcher.SecretRevisionChange) error
   130  }
   131  
   132  type newRemoteRelationsFacadeFunc func(*api.Info) (RemoteModelRelationsFacadeCloser, error)
   133  
   134  // Config defines the operation of a Worker.
   135  type Config struct {
   136  	ModelUUID                string
   137  	RelationsFacade          RemoteRelationsFacade
   138  	NewRemoteModelFacadeFunc newRemoteRelationsFacadeFunc
   139  	Clock                    clock.Clock
   140  	Logger                   Logger
   141  
   142  	// Used for testing.
   143  	Runner *worker.Runner
   144  }
   145  
   146  // Validate returns an error if config cannot drive a Worker.
   147  func (config Config) Validate() error {
   148  	if config.ModelUUID == "" {
   149  		return errors.NotValidf("empty model uuid")
   150  	}
   151  	if config.RelationsFacade == nil {
   152  		return errors.NotValidf("nil Facade")
   153  	}
   154  	if config.NewRemoteModelFacadeFunc == nil {
   155  		return errors.NotValidf("nil Remote Model Facade func")
   156  	}
   157  	if config.Clock == nil {
   158  		return errors.NotValidf("nil Clock")
   159  	}
   160  	if config.Logger == nil {
   161  		return errors.NotValidf("nil Logger")
   162  	}
   163  	return nil
   164  }
   165  
   166  // New returns a Worker backed by config, or an error.
   167  func New(config Config) (*Worker, error) {
   168  	if err := config.Validate(); err != nil {
   169  		return nil, errors.Trace(err)
   170  	}
   171  
   172  	runner := config.Runner
   173  	if runner == nil {
   174  		runner = worker.NewRunner(worker.RunnerParams{
   175  			Clock:  config.Clock,
   176  			Logger: config.Logger,
   177  
   178  			// One of the remote application workers failing should not
   179  			// prevent the others from running.
   180  			IsFatal: func(error) bool { return false },
   181  
   182  			// For any failures, try again in 15 seconds.
   183  			RestartDelay: 15 * time.Second,
   184  		})
   185  	}
   186  	w := &Worker{
   187  		config:     config,
   188  		offerUUIDs: make(map[string]string),
   189  		runner:     runner,
   190  	}
   191  	err := catacomb.Invoke(catacomb.Plan{
   192  		Site: &w.catacomb,
   193  		Work: w.loop,
   194  		Init: []worker.Worker{w.runner},
   195  	})
   196  	return w, errors.Trace(err)
   197  }
   198  
   199  // Worker manages relations and associated settings where
   200  // one end of the relation is a remote application.
   201  type Worker struct {
   202  	catacomb catacomb.Catacomb
   203  	config   Config
   204  	logger   loggo.Logger
   205  
   206  	runner *worker.Runner
   207  	mu     sync.Mutex
   208  
   209  	// offerUUIDs records the offer UUID used for each saas name.
   210  	offerUUIDs map[string]string
   211  }
   212  
   213  // Kill is defined on worker.Worker.
   214  func (w *Worker) Kill() {
   215  	w.catacomb.Kill(nil)
   216  }
   217  
   218  // Wait is defined on worker.Worker.
   219  func (w *Worker) Wait() error {
   220  	err := w.catacomb.Wait()
   221  	if err != nil {
   222  		w.logger.Errorf("error in top level remote relations worker: %v", err)
   223  	}
   224  	return err
   225  }
   226  
   227  func (w *Worker) loop() (err error) {
   228  	changes, err := w.config.RelationsFacade.WatchRemoteApplications()
   229  	if err != nil {
   230  		return errors.Trace(err)
   231  	}
   232  	if err := w.catacomb.Add(changes); err != nil {
   233  		return errors.Trace(err)
   234  	}
   235  	for {
   236  		select {
   237  		case <-w.catacomb.Dying():
   238  			return w.catacomb.ErrDying()
   239  		case applicationIds, ok := <-changes.Changes():
   240  			if !ok {
   241  				return errors.New("change channel closed")
   242  			}
   243  			err = w.handleApplicationChanges(applicationIds)
   244  			if err != nil {
   245  				return err
   246  			}
   247  		}
   248  	}
   249  }
   250  
   251  func (w *Worker) handleApplicationChanges(applicationIds []string) error {
   252  	w.mu.Lock()
   253  	defer w.mu.Unlock()
   254  
   255  	// TODO(wallyworld) - watcher should not give empty events
   256  	if len(applicationIds) == 0 {
   257  		return nil
   258  	}
   259  	logger := w.config.Logger
   260  	logger.Debugf("processing remote application changes for: %s", applicationIds)
   261  
   262  	// Fetch the current state of each of the remote applications that have changed.
   263  	results, err := w.config.RelationsFacade.RemoteApplications(applicationIds)
   264  	if err != nil {
   265  		return errors.Annotate(err, "querying remote applications")
   266  	}
   267  
   268  	for i, result := range results {
   269  		name := applicationIds[i]
   270  
   271  		// The remote application may refer to an offer that has been removed from
   272  		// the offering model, or it may refer to a new offer with a different UUID.
   273  		// If it is for a new offer, we need to stop any current worker for the old offer.
   274  		appGone := result.Error != nil && params.IsCodeNotFound(result.Error)
   275  		if result.Error != nil && !appGone {
   276  			return errors.Annotatef(result.Error, "querying remote application %q", name)
   277  		}
   278  
   279  		var remoteApp *params.RemoteApplication
   280  		offerChanged := false
   281  		if !appGone {
   282  			remoteApp = result.Result
   283  			existingOfferUUID, ok := w.offerUUIDs[result.Result.Name]
   284  			appGone = remoteApp.Status == string(status.Terminated) || remoteApp.Life == life.Dead
   285  			offerChanged = ok && existingOfferUUID != result.Result.OfferUUID
   286  		}
   287  		if appGone || offerChanged {
   288  			// The remote application has been removed, stop its worker.
   289  			logger.Debugf("saas application %q gone from offering model", name)
   290  			err := w.runner.StopAndRemoveWorker(name, w.catacomb.Dying())
   291  			if err != nil && !errors.IsNotFound(err) {
   292  				w.logger.Warningf("error stopping saas worker for %q: %v", name, err)
   293  			}
   294  			delete(w.offerUUIDs, name)
   295  			if appGone {
   296  				continue
   297  			}
   298  		}
   299  
   300  		startFunc := func() (worker.Worker, error) {
   301  			appWorker := &remoteApplicationWorker{
   302  				offerUUID:                         remoteApp.OfferUUID,
   303  				applicationName:                   remoteApp.Name,
   304  				localModelUUID:                    w.config.ModelUUID,
   305  				remoteModelUUID:                   remoteApp.ModelUUID,
   306  				isConsumerProxy:                   remoteApp.IsConsumerProxy,
   307  				consumeVersion:                    remoteApp.ConsumeVersion,
   308  				offerMacaroon:                     remoteApp.Macaroon,
   309  				localRelationUnitChanges:          make(chan RelationUnitChangeEvent),
   310  				remoteRelationUnitChanges:         make(chan RelationUnitChangeEvent),
   311  				localModelFacade:                  w.config.RelationsFacade,
   312  				newRemoteModelRelationsFacadeFunc: w.config.NewRemoteModelFacadeFunc,
   313  				logger:                            logger,
   314  			}
   315  			if err := catacomb.Invoke(catacomb.Plan{
   316  				Site: &appWorker.catacomb,
   317  				Work: appWorker.loop,
   318  			}); err != nil {
   319  				return nil, errors.Trace(err)
   320  			}
   321  			return appWorker, nil
   322  		}
   323  
   324  		logger.Debugf("starting watcher for remote application %q", name)
   325  		// Start the application worker to watch for things like new relations.
   326  		w.offerUUIDs[name] = remoteApp.OfferUUID
   327  		if err := w.runner.StartWorker(name, startFunc); err != nil {
   328  			if errors.IsAlreadyExists(err) {
   329  				w.logger.Debugf("already running remote application worker for %q", name)
   330  			} else if err != nil {
   331  				return errors.Annotate(err, "error starting remote application worker")
   332  			}
   333  		}
   334  		w.offerUUIDs[name] = remoteApp.OfferUUID
   335  	}
   336  	return nil
   337  }
   338  
   339  // Report provides information for the engine report.
   340  func (w *Worker) Report() map[string]interface{} {
   341  	result := make(map[string]interface{})
   342  	w.mu.Lock()
   343  	defer w.mu.Unlock()
   344  
   345  	saasWorkers := make(map[string]interface{})
   346  	for name := range w.offerUUIDs {
   347  		appWorker, err := w.runner.Worker(name, w.catacomb.Dying())
   348  		if err != nil {
   349  			saasWorkers[name] = fmt.Sprintf("ERROR: %v", err)
   350  			continue
   351  		}
   352  		saasWorkers[name] = appWorker.(worker.Reporter).Report()
   353  	}
   354  	result["workers"] = saasWorkers
   355  	return result
   356  }