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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"sort"
    10  	"time"
    11  
    12  	"github.com/juju/charm/v12"
    13  	"github.com/juju/collections/set"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/mgo/v3"
    16  	"github.com/juju/mgo/v3/bson"
    17  	"github.com/juju/mgo/v3/txn"
    18  	"github.com/juju/names/v5"
    19  	jujutxn "github.com/juju/txn/v3"
    20  	"gopkg.in/macaroon.v2"
    21  
    22  	"github.com/juju/juju/core/crossmodel"
    23  	"github.com/juju/juju/core/status"
    24  	"github.com/juju/juju/environs"
    25  )
    26  
    27  // RemoteApplication represents the state of an application hosted
    28  // in an external (remote) model.
    29  type RemoteApplication struct {
    30  	st  *State
    31  	doc remoteApplicationDoc
    32  }
    33  
    34  // remoteApplicationDoc represents the internal state of a remote application in MongoDB.
    35  type remoteApplicationDoc struct {
    36  	DocID                string              `bson:"_id"`
    37  	Name                 string              `bson:"name"`
    38  	OfferUUID            string              `bson:"offer-uuid"`
    39  	URL                  string              `bson:"url,omitempty"`
    40  	SourceControllerUUID string              `bson:"source-controller-uuid"`
    41  	SourceModelUUID      string              `bson:"source-model-uuid"`
    42  	Endpoints            []remoteEndpointDoc `bson:"endpoints"`
    43  	Spaces               []remoteSpaceDoc    `bson:"spaces"`
    44  	Bindings             map[string]string   `bson:"bindings"`
    45  	Life                 Life                `bson:"life"`
    46  	RelationCount        int                 `bson:"relationcount"`
    47  	IsConsumerProxy      bool                `bson:"is-consumer-proxy"`
    48  	Version              int                 `bson:"version"`
    49  	Macaroon             string              `bson:"macaroon,omitempty"`
    50  }
    51  
    52  // remoteEndpointDoc represents the internal state of a remote application endpoint in MongoDB.
    53  type remoteEndpointDoc struct {
    54  	Name      string              `bson:"name"`
    55  	Role      charm.RelationRole  `bson:"role"`
    56  	Interface string              `bson:"interface"`
    57  	Limit     int                 `bson:"limit"`
    58  	Scope     charm.RelationScope `bson:"scope"`
    59  }
    60  
    61  type attributeMap map[string]interface{}
    62  
    63  // remoteSpaceDoc represents the internal state of a space in another
    64  // model in the DB.
    65  type remoteSpaceDoc struct {
    66  	CloudType          string            `bson:"cloud-type"`
    67  	Name               string            `bson:"name"`
    68  	ProviderId         string            `bson:"provider-id"`
    69  	ProviderAttributes attributeMap      `bson:"provider-attributes"`
    70  	Subnets            []remoteSubnetDoc `bson:"subnets"`
    71  }
    72  
    73  // RemoteSpace represents a space in another model that endpoints are
    74  // bound to.
    75  type RemoteSpace struct {
    76  	CloudType          string
    77  	Name               string
    78  	ProviderId         string
    79  	ProviderAttributes attributeMap
    80  	Subnets            []RemoteSubnet
    81  }
    82  
    83  // remoteSubnetDoc represents a subnet in another model in the DB.
    84  type remoteSubnetDoc struct {
    85  	CIDR              string   `bson:"cidr"`
    86  	ProviderId        string   `bson:"provider-id"`
    87  	VLANTag           int      `bson:"vlan-tag"`
    88  	AvailabilityZones []string `bson:"availability-zones"`
    89  	ProviderSpaceId   string   `bson:"provider-space-id"`
    90  	ProviderNetworkId string   `bson:"provider-network-id"`
    91  }
    92  
    93  // RemoteSubnet represents a subnet in another model.
    94  type RemoteSubnet struct {
    95  	CIDR              string
    96  	ProviderId        string
    97  	VLANTag           int
    98  	AvailabilityZones []string
    99  	ProviderSpaceId   string
   100  	ProviderNetworkId string
   101  }
   102  
   103  func newRemoteApplication(st *State, doc *remoteApplicationDoc) *RemoteApplication {
   104  	app := &RemoteApplication{
   105  		st:  st,
   106  		doc: *doc,
   107  	}
   108  	return app
   109  }
   110  
   111  // remoteApplicationGlobalKey returns the global database key for the
   112  // remote application with the given name.
   113  //
   114  // This seems like an aggressively cryptic prefix, but apparently the
   115  // all-watcher requires that global keys have single letter prefixes
   116  // and r and a were taken.
   117  // TODO(babbageclunk): check whether this is still the case.
   118  func remoteApplicationGlobalKey(appName string) string {
   119  	return "c#" + appName
   120  }
   121  
   122  // globalKey returns the global database key for the remote application.
   123  func (a *RemoteApplication) globalKey() string {
   124  	return remoteApplicationGlobalKey(a.doc.Name)
   125  }
   126  
   127  // IsRemote returns true for a remote application.
   128  func (a *RemoteApplication) IsRemote() bool {
   129  	return true
   130  }
   131  
   132  // SourceModel returns the tag of the model to which the application belongs.
   133  func (a *RemoteApplication) SourceModel() names.ModelTag {
   134  	return names.NewModelTag(a.doc.SourceModelUUID)
   135  }
   136  
   137  // SourceController returns the UUID of the controller hosting the application.
   138  func (a *RemoteApplication) SourceController() string {
   139  	return a.doc.SourceControllerUUID
   140  }
   141  
   142  // IsConsumerProxy returns the application is created
   143  // from a registration operation by a consuming model.
   144  func (a *RemoteApplication) IsConsumerProxy() bool {
   145  	return a.doc.IsConsumerProxy
   146  }
   147  
   148  // ConsumeVersion is incremented each time a new consumer proxy
   149  // is created for an offer.
   150  func (a *RemoteApplication) ConsumeVersion() int {
   151  	return a.doc.Version
   152  }
   153  
   154  // Name returns the application name.
   155  func (a *RemoteApplication) Name() string {
   156  	return a.doc.Name
   157  }
   158  
   159  // OfferUUID returns the offer UUID.
   160  func (a *RemoteApplication) OfferUUID() string {
   161  	return a.doc.OfferUUID
   162  }
   163  
   164  // URL returns the remote application URL, and a boolean indicating whether or not
   165  // a URL is known for the remote application. A URL will only be available for the
   166  // consumer of an offered application.
   167  func (a *RemoteApplication) URL() (string, bool) {
   168  	return a.doc.URL, a.doc.URL != ""
   169  }
   170  
   171  // Token returns the token for the remote application, provided by the remote
   172  // model to identify the application in future communications.
   173  func (a *RemoteApplication) Token() (string, error) {
   174  	r := a.st.RemoteEntities()
   175  	return r.GetToken(a.Tag())
   176  }
   177  
   178  // Tag returns a name identifying the application.
   179  func (a *RemoteApplication) Tag() names.Tag {
   180  	return names.NewApplicationTag(a.Name())
   181  }
   182  
   183  // Life returns whether the application is Alive, Dying or Dead.
   184  func (a *RemoteApplication) Life() Life {
   185  	return a.doc.Life
   186  }
   187  
   188  // StatusHistory returns a slice of at most filter.Size StatusInfo items
   189  // or items as old as filter.Date or items newer than now - filter.Delta time
   190  // representing past statuses for this remote application.
   191  func (a *RemoteApplication) StatusHistory(filter status.StatusHistoryFilter) ([]status.StatusInfo, error) {
   192  	args := &statusHistoryArgs{
   193  		db:        a.st.db(),
   194  		globalKey: a.globalKey(),
   195  		filter:    filter,
   196  		clock:     a.st.clock(),
   197  	}
   198  	return statusHistory(args)
   199  }
   200  
   201  // Spaces returns the remote spaces this application is connected to.
   202  func (a *RemoteApplication) Spaces() []RemoteSpace {
   203  	var result []RemoteSpace
   204  	for _, space := range a.doc.Spaces {
   205  		result = append(result, remoteSpaceFromDoc(space))
   206  	}
   207  	return result
   208  }
   209  
   210  // Bindings returns the endpoint->space bindings for the application.
   211  func (a *RemoteApplication) Bindings() map[string]string {
   212  	result := make(map[string]string)
   213  	for epName, spName := range a.doc.Bindings {
   214  		result[epName] = spName
   215  	}
   216  	return result
   217  }
   218  
   219  // SpaceForEndpoint returns the remote space an endpoint is bound to,
   220  // if one is found.
   221  func (a *RemoteApplication) SpaceForEndpoint(endpointName string) (RemoteSpace, bool) {
   222  	spaceName, ok := a.doc.Bindings[endpointName]
   223  	if !ok {
   224  		return RemoteSpace{}, false
   225  	}
   226  	for _, space := range a.doc.Spaces {
   227  		if space.Name == spaceName {
   228  			return remoteSpaceFromDoc(space), true
   229  		}
   230  	}
   231  	return RemoteSpace{}, false
   232  }
   233  
   234  func remoteSpaceFromDoc(space remoteSpaceDoc) RemoteSpace {
   235  	result := RemoteSpace{
   236  		CloudType:          space.CloudType,
   237  		Name:               space.Name,
   238  		ProviderId:         space.ProviderId,
   239  		ProviderAttributes: copyAttributes(space.ProviderAttributes),
   240  	}
   241  	for _, subnet := range space.Subnets {
   242  		result.Subnets = append(result.Subnets, remoteSubnetFromDoc(subnet))
   243  	}
   244  	return result
   245  }
   246  
   247  func remoteSubnetFromDoc(subnet remoteSubnetDoc) RemoteSubnet {
   248  	return RemoteSubnet{
   249  		CIDR:              subnet.CIDR,
   250  		ProviderId:        subnet.ProviderId,
   251  		VLANTag:           subnet.VLANTag,
   252  		AvailabilityZones: copyStrings(subnet.AvailabilityZones),
   253  		ProviderSpaceId:   subnet.ProviderSpaceId,
   254  		ProviderNetworkId: subnet.ProviderNetworkId,
   255  	}
   256  }
   257  
   258  func copyStrings(values []string) []string {
   259  	if values == nil {
   260  		return nil
   261  	}
   262  	result := make([]string, len(values))
   263  	copy(result, values)
   264  	return result
   265  }
   266  
   267  func copyAttributes(values attributeMap) attributeMap {
   268  	if values == nil {
   269  		return nil
   270  	}
   271  	result := make(attributeMap)
   272  	for key, value := range values {
   273  		result[key] = value
   274  	}
   275  	return result
   276  }
   277  
   278  // DestroyOperation returns a model operation to destroy remote application.
   279  func (a *RemoteApplication) DestroyOperation(force bool) *DestroyRemoteApplicationOperation {
   280  	return &DestroyRemoteApplicationOperation{
   281  		app:             &RemoteApplication{st: a.st, doc: a.doc},
   282  		ForcedOperation: ForcedOperation{Force: force},
   283  	}
   284  }
   285  
   286  // DestroyRemoteApplicationOperation is a model operation to destroy a remote application.
   287  type DestroyRemoteApplicationOperation struct {
   288  	// ForcedOperation stores needed information to force this operation.
   289  	ForcedOperation
   290  
   291  	// app holds the remote application to destroy.
   292  	app *RemoteApplication
   293  }
   294  
   295  // Build is part of the ModelOperation interface.
   296  func (op *DestroyRemoteApplicationOperation) Build(attempt int) ([]txn.Op, error) {
   297  	if attempt > 0 {
   298  		if err := op.app.Refresh(); errors.IsNotFound(err) {
   299  			return nil, jujutxn.ErrNoOperations
   300  		} else if err != nil {
   301  			return nil, err
   302  		}
   303  	}
   304  	// When 'force' is set on the operation, this call will return needed operations
   305  	// and accumulate all operational errors encountered in the operation.
   306  	// If the 'force' is not set, any error will be fatal and no operations will be returned.
   307  	switch ops, err := op.destroyOps(); err {
   308  	case errRefresh:
   309  	case errAlreadyDying:
   310  		return nil, jujutxn.ErrNoOperations
   311  	case nil:
   312  		return ops, nil
   313  	default:
   314  		if op.Force {
   315  			logger.Warningf("force destroy saas application %v despite error %v", op.app, err)
   316  			return ops, nil
   317  		}
   318  		return nil, err
   319  	}
   320  	return nil, jujutxn.ErrNoOperations
   321  }
   322  
   323  // Done is part of the ModelOperation interface.
   324  func (op *DestroyRemoteApplicationOperation) Done(err error) error {
   325  	// NOTE(tsm): if you change the business logic here, check
   326  	//            that RemoveOfferOperation is modified to suit
   327  	if err != nil {
   328  		if !op.Force {
   329  			return errors.Annotatef(err, "cannot destroy saas application %q", op.app)
   330  		}
   331  		op.AddError(errors.Errorf("force destroy of saas application %v failed but proceeded despite encountering ERROR %v", op.app, err))
   332  	}
   333  	if err := op.eraseHistory(); err != nil {
   334  		if !op.Force {
   335  			logger.Errorf("cannot delete history for saas application %q: %v", op.app, err)
   336  		}
   337  		op.AddError(errors.Errorf("force erase saas application %q history proceeded despite encountering ERROR %v", op.app, err))
   338  	}
   339  	if err := op.deleteSecretReferences(); err != nil {
   340  		logger.Errorf("cannot delete secret references for saas application %q: %v", op.app, err)
   341  	}
   342  	return nil
   343  }
   344  
   345  func (op *DestroyRemoteApplicationOperation) eraseHistory() error {
   346  	var stop <-chan struct{} // stop not used here yet.
   347  	if err := eraseStatusHistory(stop, op.app.st, op.app.globalKey()); err != nil {
   348  		one := errors.Annotate(err, "saas application")
   349  		if op.FatalError(one) {
   350  			return one
   351  		}
   352  	}
   353  	return nil
   354  }
   355  
   356  func (op *DestroyRemoteApplicationOperation) deleteSecretReferences() error {
   357  	if err := op.app.st.removeRemoteSecretConsumer(op.app.Name()); err != nil {
   358  		return errors.Annotatef(err, "deleting secret consumer records for %q", op.app.Name())
   359  	}
   360  	return nil
   361  }
   362  
   363  // DestroyWithForce in addition to doing what Destroy() does,
   364  // when force is passed in as 'true', forces th destruction of remote application,
   365  // ignoring errors.
   366  func (a *RemoteApplication) DestroyWithForce(force bool, maxWait time.Duration) (opErrs []error, err error) {
   367  	defer func() {
   368  		if err == nil {
   369  			a.doc.Life = Dying
   370  		}
   371  	}()
   372  	op := a.DestroyOperation(force)
   373  	op.MaxWait = maxWait
   374  	err = a.st.ApplyOperation(op)
   375  	return op.Errors, err
   376  }
   377  
   378  // Destroy ensures that this remote application reference and all its relations
   379  // will be removed at some point; if no relation involving the
   380  // application has any units in scope, they are all removed immediately.
   381  func (a *RemoteApplication) Destroy() error {
   382  	errs, err := a.DestroyWithForce(false, time.Duration(0))
   383  	if len(errs) != 0 {
   384  		logger.Warningf("operational errors destroying saas application %v: %v", a.Name(), errs)
   385  	}
   386  	return err
   387  }
   388  
   389  // destroyOps returns the operations required to destroy the application. If it
   390  // returns errRefresh, the application should be refreshed and the destruction
   391  // operations recalculated.
   392  // When 'force' is set, this call will return needed operations
   393  // and accumulate all operational errors encountered in the operation.
   394  // If the 'force' is not set, any error will be fatal and no operations will be returned.
   395  func (op *DestroyRemoteApplicationOperation) destroyOps() (ops []txn.Op, err error) {
   396  	if op.app.doc.Life == Dying {
   397  		if !op.Force {
   398  			return nil, errAlreadyDying
   399  		}
   400  	}
   401  	haveRels := true
   402  	rels, err := op.app.Relations()
   403  	if op.FatalError(err) {
   404  		return nil, errors.Trace(err)
   405  	}
   406  	if err != nil {
   407  		haveRels = false
   408  	}
   409  
   410  	// We'll need status below when processing relations.
   411  	statusInfo, statusErr := op.app.Status()
   412  	if op.FatalError(statusErr) && !errors.IsNotFound(statusErr) {
   413  		return nil, statusErr
   414  	}
   415  	// If the application is already terminated and dead, the removal
   416  	// can be short circuited.
   417  	forceTerminate := op.Force || statusInfo.Status == status.Terminated
   418  
   419  	if !forceTerminate && haveRels && len(rels) != op.app.doc.RelationCount {
   420  		// This is just an early bail out. The relations obtained may still
   421  		// be wrong, but that situation will be caught by a combination of
   422  		// asserts on relationcount and on each known relation, below.
   423  		return nil, errRefresh
   424  	}
   425  
   426  	op.ForcedOperation.Force = forceTerminate
   427  	removeCount := 0
   428  	if haveRels {
   429  		failRels := false
   430  		for _, rel := range rels {
   431  			// If the remote app has been terminated, we may have been offline
   432  			// and not noticed so need to clean up any exiting relation units.
   433  			destroyRelUnitOps, err := destroyCrossModelRelationUnitsOps(&op.ForcedOperation, op.app, rel, true)
   434  			if err != nil && err != jujutxn.ErrNoOperations {
   435  				return nil, errors.Trace(err)
   436  			}
   437  			ops = append(ops, destroyRelUnitOps...)
   438  			// When 'force' is set, this call will return both needed operations
   439  			// as well as all operational errors encountered.
   440  			// If the 'force' is not set, any error will be fatal and no operations will be returned.
   441  			relOps, isRemove, err := rel.destroyOps(op.app.doc.Name, &op.ForcedOperation)
   442  			if err == errAlreadyDying {
   443  				relOps = []txn.Op{{
   444  					C:      relationsC,
   445  					Id:     rel.doc.DocID,
   446  					Assert: bson.D{{"life", Dying}},
   447  				}}
   448  			} else if err != nil {
   449  				op.AddError(err)
   450  				failRels = true
   451  				continue
   452  			}
   453  			if isRemove {
   454  				removeCount++
   455  			}
   456  			ops = append(ops, relOps...)
   457  		}
   458  		if !op.Force && failRels {
   459  			return nil, errors.Trace(op.LastError())
   460  		}
   461  	}
   462  	// If all of the application's known relations will be
   463  	// removed, the application can also be removed.
   464  	if forceTerminate || op.app.doc.RelationCount == removeCount {
   465  		var hasLastRefs bson.D
   466  		if !forceTerminate {
   467  			hasLastRefs = bson.D{{"life", Alive}, {"relationcount", removeCount}}
   468  		}
   469  		removeOps, err := op.app.removeOps(hasLastRefs)
   470  		if err != nil {
   471  			return nil, errors.Trace(err)
   472  		}
   473  		ops = append(ops, removeOps...)
   474  		return ops, nil
   475  	}
   476  	// In all other cases, application removal will be handled as a consequence
   477  	// of the removal of the relation referencing it. If any  relations have
   478  	// been removed, they'll be caught by the operations collected above;
   479  	// but if any has been added, we need to abort and add  a destroy op for
   480  	// that relation too.
   481  	// In combination, it's enough to check for count equality:
   482  	// an add/remove will not touch the count, but  will be caught by
   483  	// virtue of being a remove.
   484  	notLastRefs := bson.D{
   485  		{"life", Alive},
   486  		{"relationcount", op.app.doc.RelationCount},
   487  	}
   488  	update := bson.D{{"$set", bson.D{{"life", Dying}}}}
   489  	if removeCount != 0 {
   490  		decref := bson.D{{"$inc", bson.D{{"relationcount", -removeCount}}}}
   491  		update = append(update, decref...)
   492  	}
   493  	ops = append(ops, txn.Op{
   494  		C:      remoteApplicationsC,
   495  		Id:     op.app.doc.DocID,
   496  		Assert: notLastRefs,
   497  		Update: update,
   498  	})
   499  	return ops, nil
   500  }
   501  
   502  // removeOps returns the operations required to remove the application. Supplied
   503  // asserts will be included in the operation on the application document.
   504  func (a *RemoteApplication) removeOps(asserts bson.D) ([]txn.Op, error) {
   505  	r := a.st.RemoteEntities()
   506  	ops := []txn.Op{
   507  		{
   508  			C:      remoteApplicationsC,
   509  			Id:     a.doc.DocID,
   510  			Assert: asserts,
   511  			Remove: true,
   512  		},
   513  		removeStatusOp(a.st, a.globalKey()),
   514  	}
   515  	tokenOps := r.removeRemoteEntityOps(a.Tag())
   516  	ops = append(ops, tokenOps...)
   517  
   518  	secretConsumerPermissionsOps, err := a.st.removeConsumerSecretPermissionOps(a.Tag())
   519  	if err != nil {
   520  		return nil, errors.Annotatef(err, "deleting secret consumer records for %q", a.Name())
   521  	}
   522  	ops = append(ops, secretConsumerPermissionsOps...)
   523  
   524  	// If this is the last consumed app off an external controller,
   525  	// also remove the external controller record.
   526  	if a.doc.SourceControllerUUID != "" {
   527  		decRefOp, isFinal, err := decExternalControllersRefOp(a.st, a.doc.SourceControllerUUID)
   528  		if err != nil {
   529  			return nil, errors.Trace(err)
   530  		}
   531  		ops = append(ops, decRefOp)
   532  		if isFinal {
   533  			ops = append(ops, txn.Op{
   534  				C:      externalControllersC,
   535  				Id:     a.doc.SourceControllerUUID,
   536  				Remove: true,
   537  			})
   538  		}
   539  	}
   540  	return ops, nil
   541  }
   542  
   543  // Status returns the status of the remote application.
   544  func (a *RemoteApplication) Status() (status.StatusInfo, error) {
   545  	return getStatus(a.st.db(), a.globalKey(), fmt.Sprintf("saas application %q", a.doc.Name))
   546  }
   547  
   548  // SetStatus sets the status for the application.
   549  func (a *RemoteApplication) SetStatus(info status.StatusInfo) error {
   550  	// We only care about status for alive apps; we want to
   551  	// avoid stray updates from the other model.
   552  	if a.Life() != Alive {
   553  		return nil
   554  	}
   555  	if !info.Status.KnownWorkloadStatus() {
   556  		return errors.Errorf("cannot set invalid status %q", info.Status)
   557  	}
   558  
   559  	return setStatus(a.st.db(), setStatusParams{
   560  		badge:     fmt.Sprintf("saas application %q", a.doc.Name),
   561  		globalKey: a.globalKey(),
   562  		status:    info.Status,
   563  		message:   info.Message,
   564  		rawData:   info.Data,
   565  		updated:   timeOrNow(info.Since, a.st.clock()),
   566  	})
   567  }
   568  
   569  // TerminateOperation returns a ModelOperation that will terminate this
   570  // remote application when applied, ensuring that all units have left
   571  // scope as well.
   572  func (a *RemoteApplication) TerminateOperation(message string) ModelOperation {
   573  	return &terminateRemoteApplicationOperation{
   574  		app: a,
   575  		doc: statusDoc{
   576  			Status:     status.Terminated,
   577  			StatusInfo: message,
   578  			Updated:    a.st.clock().Now().UnixNano(),
   579  		},
   580  	}
   581  }
   582  
   583  type terminateRemoteApplicationOperation struct {
   584  	app *RemoteApplication
   585  	doc statusDoc
   586  }
   587  
   588  // Build is part of ModelOperation.
   589  func (op *terminateRemoteApplicationOperation) Build(attempt int) ([]txn.Op, error) {
   590  	if attempt > 0 {
   591  		err := op.app.Refresh()
   592  		if err != nil && !errors.IsNotFound(err) {
   593  			return nil, errors.Trace(err)
   594  		}
   595  		if err != nil || op.app.Life() == Dead {
   596  			return nil, jujutxn.ErrNoOperations
   597  		}
   598  	}
   599  	ops, err := statusSetOps(op.app.st.db(), op.doc, op.app.globalKey())
   600  	if err != nil {
   601  		return nil, errors.Annotate(err, "setting status")
   602  	}
   603  	// Strictly speaking, we should transition through Dying state.
   604  	ops = append(ops, txn.Op{
   605  		C:      remoteApplicationsC,
   606  		Id:     op.app.doc.DocID,
   607  		Assert: notDeadDoc,
   608  		Update: bson.D{{"$set", bson.D{{"life", Dying}}}},
   609  	})
   610  	name := op.app.Name()
   611  	logger.Debugf("leaving scope on all %q relation units", name)
   612  	rels, err := op.app.Relations()
   613  	if err != nil {
   614  		return nil, errors.Annotatef(err, "getting relations for %q", name)
   615  	}
   616  	// Termination happens when the offer has disappeared so we can force destroy any
   617  	// relations on the consuming side.
   618  	// Destroying each relation also forces remote units to leave scope.
   619  	for _, rel := range rels {
   620  		relOps, err := destroyCrossModelRelationUnitsOps(&ForcedOperation{Force: true}, op.app, rel, false)
   621  		if err != nil && err != jujutxn.ErrNoOperations {
   622  			return nil, errors.Annotatef(err, "removing relation %q", rel)
   623  		}
   624  		ops = append(ops, relOps...)
   625  	}
   626  	return ops, nil
   627  }
   628  
   629  // Done is part of ModelOperation.
   630  func (op *terminateRemoteApplicationOperation) Done(err error) error {
   631  	if err != nil {
   632  		return errors.Annotatef(err, "terminating saas application %q", op.app.Name())
   633  	}
   634  	_, _ = probablyUpdateStatusHistory(op.app.st.db(), op.app.globalKey(), op.doc)
   635  	// Set the life to Dead so that the lifecycle watcher will trigger to inform the
   636  	// relevant workers that this application is gone.
   637  	ops := []txn.Op{{
   638  		C:      remoteApplicationsC,
   639  		Id:     op.app.doc.DocID,
   640  		Update: bson.D{{"$set", bson.D{{"life", Dead}}}},
   641  	}}
   642  	return op.app.st.db().RunTransaction(ops)
   643  }
   644  
   645  // Endpoints returns the application's currently available relation endpoints.
   646  func (a *RemoteApplication) Endpoints() ([]Endpoint, error) {
   647  	return remoteEndpointDocsToEndpoints(a.Name(), a.doc.Endpoints), nil
   648  }
   649  
   650  func remoteEndpointDocsToEndpoints(applicationName string, docs []remoteEndpointDoc) []Endpoint {
   651  	eps := make([]Endpoint, len(docs))
   652  	for i, ep := range docs {
   653  		eps[i] = Endpoint{
   654  			ApplicationName: applicationName,
   655  			Relation: charm.Relation{
   656  				Name:      ep.Name,
   657  				Role:      ep.Role,
   658  				Interface: ep.Interface,
   659  				Limit:     ep.Limit,
   660  				Scope:     ep.Scope,
   661  			}}
   662  	}
   663  	sort.Sort(epSlice(eps))
   664  	return eps
   665  }
   666  
   667  // Endpoint returns the relation endpoint with the supplied name, if it exists.
   668  func (a *RemoteApplication) Endpoint(relationName string) (Endpoint, error) {
   669  	eps, err := a.Endpoints()
   670  	if err != nil {
   671  		return Endpoint{}, err
   672  	}
   673  	for _, ep := range eps {
   674  		if ep.Name == relationName {
   675  			return ep, nil
   676  		}
   677  	}
   678  	return Endpoint{}, fmt.Errorf("saas application %q has no %q relation", a, relationName)
   679  }
   680  
   681  // AddEndpoints adds the specified endpoints to the remote application.
   682  // If an endpoint with the same name already exists, an error is returned.
   683  // If the endpoints change during the update, the operation is retried.
   684  func (a *RemoteApplication) AddEndpoints(eps []charm.Relation) error {
   685  	newEps := make([]remoteEndpointDoc, len(eps))
   686  	for i, ep := range eps {
   687  		newEps[i] = remoteEndpointDoc{
   688  			Name:      ep.Name,
   689  			Role:      ep.Role,
   690  			Interface: ep.Interface,
   691  			Limit:     ep.Limit,
   692  			Scope:     ep.Scope,
   693  		}
   694  	}
   695  
   696  	model, err := a.st.Model()
   697  	if err != nil {
   698  		return errors.Trace(err)
   699  	} else if model.Life() != Alive {
   700  		return errors.Errorf("model is no longer alive")
   701  	}
   702  
   703  	checkCompatibleEndpoints := func(currentEndpoints []Endpoint) error {
   704  		// Ensure there are no current endpoints with the same name as
   705  		// any of those we want to update.
   706  		currentEndpointNames := set.NewStrings()
   707  		for _, ep := range currentEndpoints {
   708  			currentEndpointNames.Add(ep.Name)
   709  		}
   710  		for _, r := range eps {
   711  			if currentEndpointNames.Contains(r.Name) {
   712  				return errors.AlreadyExistsf("endpoint %v", r.Name)
   713  			}
   714  		}
   715  		return nil
   716  	}
   717  
   718  	currentEndpoints, err := a.Endpoints()
   719  	if err != nil {
   720  		return errors.Trace(err)
   721  	}
   722  	if err := checkCompatibleEndpoints(currentEndpoints); err != nil {
   723  		return err
   724  	}
   725  	applicationID := a.st.docID(a.Name())
   726  	buildTxn := func(attempt int) ([]txn.Op, error) {
   727  		// If we've tried once already and failed, check that
   728  		// model may have been destroyed.
   729  		if attempt > 0 {
   730  			if err := checkModelActive(a.st); err != nil {
   731  				return nil, errors.Trace(err)
   732  			}
   733  			if err = a.Refresh(); err != nil {
   734  				return nil, errors.Trace(err)
   735  			}
   736  			currentEndpoints, err = a.Endpoints()
   737  			if err != nil {
   738  				return nil, errors.Trace(err)
   739  			}
   740  			if err := checkCompatibleEndpoints(currentEndpoints); err != nil {
   741  				return nil, err
   742  			}
   743  		}
   744  		ops := []txn.Op{
   745  			model.assertActiveOp(),
   746  			{
   747  				C:  remoteApplicationsC,
   748  				Id: applicationID,
   749  				Assert: bson.D{
   750  					{"endpoints", bson.D{{
   751  						"$not", bson.D{{
   752  							"$elemMatch", bson.D{{
   753  								"$in", newEps}},
   754  						}},
   755  					}}},
   756  				},
   757  				Update: bson.D{
   758  					{"$addToSet", bson.D{{"endpoints", bson.D{{"$each", newEps}}}}},
   759  				},
   760  			},
   761  		}
   762  		return ops, nil
   763  	}
   764  	if err := a.st.db().Run(buildTxn); err != nil {
   765  		return errors.Trace(err)
   766  	}
   767  	return a.Refresh()
   768  }
   769  
   770  // SetSourceController updates the source controller attribute.
   771  func (a *RemoteApplication) SetSourceController(sourceControllerUUID string) error {
   772  	model, err := a.st.Model()
   773  	if err != nil {
   774  		return errors.Trace(err)
   775  	} else if model.Life() != Alive {
   776  		return errors.Errorf("model is no longer alive")
   777  	}
   778  
   779  	applicationID := a.st.docID(a.Name())
   780  	buildTxn := func(attempt int) ([]txn.Op, error) {
   781  		// If we've tried once already and failed, check that
   782  		// model may have been destroyed.
   783  		if attempt > 0 {
   784  			if model.Life() == Dead {
   785  				return nil, errors.Errorf("model %q is %s", model.Name(), model.Life().String())
   786  			}
   787  			if err = a.Refresh(); err != nil {
   788  				return nil, errors.Trace(err)
   789  			}
   790  		}
   791  		ops := []txn.Op{
   792  			{
   793  				C:      modelsC,
   794  				Id:     model.UUID(),
   795  				Assert: notDeadDoc,
   796  			}, {
   797  				C:      remoteApplicationsC,
   798  				Id:     applicationID,
   799  				Assert: txn.DocExists,
   800  				Update: bson.D{
   801  					{"$set", bson.D{{"source-controller-uuid", sourceControllerUUID}}},
   802  				},
   803  			},
   804  		}
   805  		return ops, nil
   806  	}
   807  	if err := a.st.db().Run(buildTxn); err != nil {
   808  		return errors.Trace(err)
   809  	}
   810  	return a.Refresh()
   811  }
   812  
   813  func (a *RemoteApplication) Macaroon() (*macaroon.Macaroon, error) {
   814  	if a.doc.Macaroon == "" {
   815  		return nil, nil
   816  	}
   817  	var mac macaroon.Macaroon
   818  	err := json.Unmarshal([]byte(a.doc.Macaroon), &mac)
   819  	if err != nil {
   820  		return nil, errors.Trace(err)
   821  	}
   822  	return &mac, nil
   823  }
   824  
   825  // String returns the application name.
   826  func (a *RemoteApplication) String() string {
   827  	return a.doc.Name
   828  }
   829  
   830  // Refresh refreshes the contents of the RemoteApplication from the underlying
   831  // state. It returns an error that satisfies errors.IsNotFound if the
   832  // application has been removed.
   833  func (a *RemoteApplication) Refresh() error {
   834  	applications, closer := a.st.db().GetCollection(remoteApplicationsC)
   835  	defer closer()
   836  
   837  	err := applications.FindId(a.doc.DocID).One(&a.doc)
   838  	if err == mgo.ErrNotFound {
   839  		return errors.NotFoundf("saas application %q", a)
   840  	}
   841  	if err != nil {
   842  		return fmt.Errorf("cannot refresh application %q: %v", a, err)
   843  	}
   844  	return nil
   845  }
   846  
   847  // Relations returns a Relation for every relation the application is in.
   848  func (a *RemoteApplication) Relations() (relations []*Relation, err error) {
   849  	return matchingRelations(a.st, a.doc.Name)
   850  }
   851  
   852  // AddRemoteApplicationParams contains the parameters for adding a remote application
   853  // to the model.
   854  type AddRemoteApplicationParams struct {
   855  	// Name is the name to give the remote application. This does not have to
   856  	// match the application name in the URL, or the name in the remote model.
   857  	Name string
   858  
   859  	// OfferUUID is the UUID of the offer.
   860  	OfferUUID string
   861  
   862  	// URL is either empty, or the URL that the remote application was offered
   863  	// with on the hosting model.
   864  	URL string
   865  
   866  	// ExternalControllerUUID, if set, is the UUID of the controller other
   867  	// than this one, which is hosting the offer.
   868  	ExternalControllerUUID string
   869  
   870  	// SourceModel is the tag of the model to which the remote application belongs.
   871  	SourceModel names.ModelTag
   872  
   873  	// Token is an opaque string that identifies the remote application in the
   874  	// source model.
   875  	Token string
   876  
   877  	// Endpoints describes the endpoints that the remote application implements.
   878  	Endpoints []charm.Relation
   879  
   880  	// Spaces describes the network spaces that the remote
   881  	// application's endpoints inhabit in the remote model.
   882  	Spaces []*environs.ProviderSpaceInfo
   883  
   884  	// Bindings maps each endpoint name to the remote space it is bound to.
   885  	Bindings map[string]string
   886  
   887  	// IsConsumerProxy is true when a remote application is created as a result
   888  	// of a registration operation from a remote model.
   889  	IsConsumerProxy bool
   890  
   891  	// ConsumeVersion is incremented each time a new consumer proxy
   892  	// is created for an offer.
   893  	ConsumeVersion int
   894  
   895  	// Macaroon is used for authentication on the offering side.
   896  	Macaroon *macaroon.Macaroon
   897  }
   898  
   899  // Validate returns an error if there's a problem with the
   900  // parameters being used to create a remote application.
   901  func (p AddRemoteApplicationParams) Validate() error {
   902  	if !names.IsValidApplication(p.Name) {
   903  		return errors.NotValidf("name %q", p.Name)
   904  	}
   905  	if p.URL != "" {
   906  		// URL may be empty, to represent remote applications corresponding
   907  		// to consumers of an offered application.
   908  		if _, err := crossmodel.ParseOfferURL(p.URL); err != nil {
   909  			return errors.Annotate(err, "validating offer URL")
   910  		}
   911  	}
   912  	if p.SourceModel == (names.ModelTag{}) {
   913  		return errors.NotValidf("empty source model tag")
   914  	}
   915  	spaceNames := set.NewStrings()
   916  	for _, space := range p.Spaces {
   917  		spaceNames.Add(string(space.Name))
   918  	}
   919  	for endpoint, space := range p.Bindings {
   920  		if !spaceNames.Contains(space) {
   921  			return errors.NotValidf("endpoint %q bound to missing space %q", endpoint, space)
   922  		}
   923  	}
   924  	return nil
   925  }
   926  
   927  // AddRemoteApplication creates a new remote application record,
   928  // having the supplied relation endpoints, with the supplied name,
   929  // which must be unique across all applications, local and remote.
   930  func (st *State) AddRemoteApplication(args AddRemoteApplicationParams) (_ *RemoteApplication, err error) {
   931  	defer errors.DeferredAnnotatef(&err, "cannot add saas application %q", args.Name)
   932  
   933  	// Sanity checks.
   934  	if err := args.Validate(); err != nil {
   935  		return nil, errors.Trace(err)
   936  	}
   937  	model, err := st.Model()
   938  	if err != nil {
   939  		return nil, errors.Trace(err)
   940  	} else if model.Life() != Alive {
   941  		return nil, errors.Errorf("model is no longer alive")
   942  	}
   943  
   944  	var macJSON string
   945  	if args.Macaroon != nil {
   946  		b, err := json.Marshal(args.Macaroon)
   947  		if err != nil {
   948  			return nil, errors.Trace(err)
   949  		}
   950  		macJSON = string(b)
   951  	}
   952  	applicationID := st.docID(args.Name)
   953  	// Create the application addition operations.
   954  	appDoc := &remoteApplicationDoc{
   955  		DocID:                applicationID,
   956  		Name:                 args.Name,
   957  		SourceControllerUUID: args.ExternalControllerUUID,
   958  		SourceModelUUID:      args.SourceModel.Id(),
   959  		URL:                  args.URL,
   960  		Bindings:             args.Bindings,
   961  		Life:                 Alive,
   962  		IsConsumerProxy:      args.IsConsumerProxy,
   963  		Version:              args.ConsumeVersion,
   964  		Macaroon:             macJSON,
   965  	}
   966  	if !args.IsConsumerProxy {
   967  		if appDoc.Version, err = sequenceWithMin(st, args.OfferUUID, 1); err != nil {
   968  			return nil, errors.Trace(err)
   969  		}
   970  		appDoc.OfferUUID = args.OfferUUID
   971  	}
   972  	eps := make([]remoteEndpointDoc, len(args.Endpoints))
   973  	for i, ep := range args.Endpoints {
   974  		eps[i] = remoteEndpointDoc{
   975  			Name:      ep.Name,
   976  			Role:      ep.Role,
   977  			Interface: ep.Interface,
   978  			Limit:     ep.Limit,
   979  			Scope:     ep.Scope,
   980  		}
   981  	}
   982  	appDoc.Endpoints = eps
   983  	spaces := make([]remoteSpaceDoc, len(args.Spaces))
   984  	for i, space := range args.Spaces {
   985  		spaces[i] = remoteSpaceDoc{
   986  			CloudType:          space.CloudType,
   987  			Name:               string(space.Name),
   988  			ProviderId:         string(space.ProviderId),
   989  			ProviderAttributes: space.ProviderAttributes,
   990  		}
   991  		subnets := make([]remoteSubnetDoc, len(space.Subnets))
   992  		for i, subnet := range space.Subnets {
   993  			subnets[i] = remoteSubnetDoc{
   994  				CIDR:              subnet.CIDR,
   995  				ProviderId:        string(subnet.ProviderId),
   996  				VLANTag:           subnet.VLANTag,
   997  				AvailabilityZones: copyStrings(subnet.AvailabilityZones),
   998  				ProviderSpaceId:   string(subnet.ProviderSpaceId),
   999  				ProviderNetworkId: string(subnet.ProviderNetworkId),
  1000  			}
  1001  		}
  1002  		spaces[i].Subnets = subnets
  1003  	}
  1004  	appDoc.Spaces = spaces
  1005  	app := newRemoteApplication(st, appDoc)
  1006  
  1007  	buildTxn := func(attempt int) ([]txn.Op, error) {
  1008  		// If we've tried once already and failed, check that
  1009  		// model may have been destroyed.
  1010  		if attempt > 0 {
  1011  			if err := checkModelActive(st); err != nil {
  1012  				return nil, errors.Trace(err)
  1013  			}
  1014  			// Ensure a local application with the same name doesn't exist.
  1015  			if localExists, err := isNotDead(st, applicationsC, args.Name); err != nil {
  1016  				return nil, errors.Trace(err)
  1017  			} else if localExists {
  1018  				return nil, errors.AlreadyExistsf("local application with same name")
  1019  			}
  1020  			// Ensure a remote application with the same name doesn't exist.
  1021  			if exists, err := isNotDead(st, remoteApplicationsC, args.Name); err != nil {
  1022  				return nil, errors.Trace(err)
  1023  			} else if exists {
  1024  				return nil, errors.AlreadyExistsf("saas application")
  1025  			}
  1026  		}
  1027  		ops := []txn.Op{
  1028  			model.assertActiveOp(),
  1029  			{
  1030  				C:      remoteApplicationsC,
  1031  				Id:     appDoc.Name,
  1032  				Assert: txn.DocMissing,
  1033  				Insert: appDoc,
  1034  			}, {
  1035  				C:      applicationsC,
  1036  				Id:     appDoc.Name,
  1037  				Assert: txn.DocMissing,
  1038  			},
  1039  		}
  1040  		if !args.IsConsumerProxy {
  1041  			statusDoc := statusDoc{
  1042  				ModelUUID: st.ModelUUID(),
  1043  				Status:    status.Unknown,
  1044  				Updated:   st.clock().Now().UnixNano(),
  1045  			}
  1046  			ops = append(ops, createStatusOp(st, app.globalKey(), statusDoc))
  1047  		}
  1048  		// If we know the token, import it.
  1049  		if args.Token != "" {
  1050  			importRemoteEntityOps := st.RemoteEntities().importRemoteEntityOps(app.Tag(), args.Token)
  1051  			ops = append(ops, importRemoteEntityOps...)
  1052  		}
  1053  
  1054  		if args.ExternalControllerUUID != "" {
  1055  			incRefOp, err := incExternalControllersRefOp(st, args.ExternalControllerUUID)
  1056  			if err != nil {
  1057  				return nil, errors.Trace(err)
  1058  			}
  1059  			ops = append(ops, incRefOp)
  1060  		}
  1061  		return ops, nil
  1062  	}
  1063  	if err = st.db().Run(buildTxn); err != nil {
  1064  		return nil, errors.Trace(err)
  1065  	}
  1066  	return app, nil
  1067  }
  1068  
  1069  // RemoteApplication returns a remote application state by name.
  1070  func (st *State) RemoteApplication(name string) (_ *RemoteApplication, err error) {
  1071  	if !names.IsValidApplication(name) {
  1072  		return nil, errors.NotValidf("saas application name %q", name)
  1073  	}
  1074  
  1075  	applications, closer := st.db().GetCollection(remoteApplicationsC)
  1076  	defer closer()
  1077  
  1078  	appDoc := &remoteApplicationDoc{}
  1079  	err = applications.FindId(name).One(appDoc)
  1080  	if err == mgo.ErrNotFound {
  1081  		return nil, errors.NotFoundf("saas application %q", name)
  1082  	}
  1083  	if err != nil {
  1084  		return nil, errors.Annotatef(err, "cannot get saas application %q", name)
  1085  	}
  1086  	return newRemoteApplication(st, appDoc), nil
  1087  }
  1088  
  1089  // AllRemoteApplications returns all the remote applications used by the model.
  1090  func (st *State) AllRemoteApplications() (applications []*RemoteApplication, err error) {
  1091  	applicationsCollection, closer := st.db().GetCollection(remoteApplicationsC)
  1092  	defer closer()
  1093  
  1094  	appDocs := []remoteApplicationDoc{}
  1095  	err = applicationsCollection.Find(bson.D{}).All(&appDocs)
  1096  	if err != nil {
  1097  		return nil, errors.Errorf("cannot get all saas applications")
  1098  	}
  1099  	for _, v := range appDocs {
  1100  		applications = append(applications, newRemoteApplication(st, &v))
  1101  	}
  1102  	return applications, nil
  1103  }