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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/mgo/v3"
    11  	"github.com/juju/mgo/v3/bson"
    12  	"github.com/juju/mgo/v3/txn"
    13  	"github.com/juju/names/v5"
    14  
    15  	"github.com/juju/juju/core/status"
    16  )
    17  
    18  // OfferConnection represents the state of a relation
    19  // to an offer hosted in this model.
    20  type OfferConnection struct {
    21  	st  *State
    22  	doc offerConnectionDoc
    23  }
    24  
    25  // offerConnectionDoc represents the internal state of an offer connection in MongoDB.
    26  type offerConnectionDoc struct {
    27  	DocID           string `bson:"_id"`
    28  	RelationId      int    `bson:"relation-id"`
    29  	RelationKey     string `bson:"relation-key"`
    30  	OfferUUID       string `bson:"offer-uuid"`
    31  	UserName        string `bson:"username"`
    32  	SourceModelUUID string `bson:"source-model-uuid"`
    33  }
    34  
    35  func newOfferConnection(st *State, doc *offerConnectionDoc) *OfferConnection {
    36  	app := &OfferConnection{
    37  		st:  st,
    38  		doc: *doc,
    39  	}
    40  	return app
    41  }
    42  
    43  // OfferUUID returns the offer UUID.
    44  func (oc *OfferConnection) OfferUUID() string {
    45  	return oc.doc.OfferUUID
    46  }
    47  
    48  // UserName returns the name of the user who created this connection.
    49  func (oc *OfferConnection) UserName() string {
    50  	return oc.doc.UserName
    51  }
    52  
    53  // RelationId is the id of the relation to which this connection pertains.
    54  func (oc *OfferConnection) RelationId() int {
    55  	return oc.doc.RelationId
    56  }
    57  
    58  // SourceModelUUID is the uuid of the consuming model.
    59  func (oc *OfferConnection) SourceModelUUID() string {
    60  	return oc.doc.SourceModelUUID
    61  }
    62  
    63  // RelationKey is the key of the relation to which this connection pertains.
    64  func (oc *OfferConnection) RelationKey() string {
    65  	return oc.doc.RelationKey
    66  }
    67  
    68  func removeOfferConnectionsForRelationOps(relId int) []txn.Op {
    69  	op := txn.Op{
    70  		C:      offerConnectionsC,
    71  		Id:     fmt.Sprintf("%d", relId),
    72  		Remove: true,
    73  	}
    74  	return []txn.Op{op}
    75  }
    76  
    77  // String returns the details of the connection.
    78  func (oc *OfferConnection) String() string {
    79  	return fmt.Sprintf("connection to %q by %q for relation %d", oc.doc.OfferUUID, oc.doc.UserName, oc.doc.RelationId)
    80  }
    81  
    82  // AddOfferConnectionParams contains the parameters for adding an offer connection
    83  // to the model.
    84  type AddOfferConnectionParams struct {
    85  	// SourceModelUUID is the UUID of the consuming model.
    86  	SourceModelUUID string
    87  
    88  	// OfferUUID is the UUID of the offer.
    89  	OfferUUID string
    90  
    91  	// Username is the name of the user who created this connection.
    92  	Username string
    93  
    94  	// RelationId is the id of the relation to which this offer pertains.
    95  	RelationId int
    96  
    97  	// RelationKey is the key of the relation to which this offer pertains.
    98  	RelationKey string
    99  }
   100  
   101  func validateOfferConnectionParams(args AddOfferConnectionParams) (err error) {
   102  	// Sanity checks.
   103  	if !names.IsValidModel(args.SourceModelUUID) {
   104  		return errors.NotValidf("source model %q", args.SourceModelUUID)
   105  	}
   106  	if !names.IsValidUser(args.Username) {
   107  		return errors.NotValidf("offer connection user %q", args.Username)
   108  	}
   109  	return nil
   110  }
   111  
   112  // AddOfferConnection creates a new offer connection record, which records details about a
   113  // relation made from a remote model to an offer in the local model.
   114  func (st *State) AddOfferConnection(args AddOfferConnectionParams) (_ *OfferConnection, err error) {
   115  	defer errors.DeferredAnnotatef(&err, "cannot add offer record for %q", args.OfferUUID)
   116  
   117  	if err := validateOfferConnectionParams(args); err != nil {
   118  		return nil, errors.Trace(err)
   119  	}
   120  
   121  	model, err := st.Model()
   122  	if err != nil {
   123  		return nil, errors.Trace(err)
   124  	} else if model.Life() != Alive {
   125  		return nil, errors.Errorf("model is no longer alive")
   126  	}
   127  
   128  	// Create the application addition operations.
   129  	offerConnectionDoc := offerConnectionDoc{
   130  		SourceModelUUID: args.SourceModelUUID,
   131  		OfferUUID:       args.OfferUUID,
   132  		UserName:        args.Username,
   133  		RelationId:      args.RelationId,
   134  		RelationKey:     args.RelationKey,
   135  		DocID:           fmt.Sprintf("%d", args.RelationId),
   136  	}
   137  	buildTxn := func(attempt int) ([]txn.Op, error) {
   138  		// If we've tried once already and failed, check that
   139  		// model may have been destroyed.
   140  		if attempt > 0 {
   141  			if err := checkModelActive(st); err != nil {
   142  				return nil, errors.Trace(err)
   143  			}
   144  			return nil, errors.AlreadyExistsf("offer connection for relation id %d", args.RelationId)
   145  		}
   146  		ops := []txn.Op{
   147  			model.assertActiveOp(),
   148  			{
   149  				C:      offerConnectionsC,
   150  				Id:     offerConnectionDoc.DocID,
   151  				Assert: txn.DocMissing,
   152  				Insert: &offerConnectionDoc,
   153  			},
   154  		}
   155  		return ops, nil
   156  	}
   157  	if err = st.db().Run(buildTxn); err != nil {
   158  		return nil, errors.Trace(err)
   159  	}
   160  	return &OfferConnection{doc: offerConnectionDoc}, nil
   161  }
   162  
   163  // AllOfferConnections returns all offer connections in the model.
   164  func (st *State) AllOfferConnections() ([]*OfferConnection, error) {
   165  	conns, err := st.offerConnections(nil)
   166  	return conns, errors.Annotate(err, "getting offer connections")
   167  }
   168  
   169  // OfferConnections returns the offer connections for an offer.
   170  func (st *State) OfferConnections(offerUUID string) ([]*OfferConnection, error) {
   171  	conns, err := st.offerConnections(bson.D{{"offer-uuid", offerUUID}})
   172  	return conns, errors.Annotatef(err, "getting offer connections for %v", offerUUID)
   173  }
   174  
   175  // OfferConnectionsForUser returns the offer connections for the specified user.
   176  func (st *State) OfferConnectionsForUser(username string) ([]*OfferConnection, error) {
   177  	conns, err := st.offerConnections(bson.D{{"username", username}})
   178  	return conns, errors.Annotatef(err, "getting offer connections for user %q", username)
   179  }
   180  
   181  // offerConnections returns the offer connections for the input condition
   182  func (st *State) offerConnections(condition bson.D) ([]*OfferConnection, error) {
   183  	offerConnectionCollection, closer := st.db().GetCollection(offerConnectionsC)
   184  	defer closer()
   185  
   186  	var connDocs []offerConnectionDoc
   187  	if err := offerConnectionCollection.Find(condition).All(&connDocs); err != nil {
   188  		return nil, errors.Trace(err)
   189  	}
   190  
   191  	conns := make([]*OfferConnection, len(connDocs))
   192  	for i, v := range connDocs {
   193  		conns[i] = newOfferConnection(st, &v)
   194  	}
   195  	return conns, nil
   196  }
   197  
   198  // OfferConnectionForRelation returns the offer connection for the specified relation.
   199  func (st *State) OfferConnectionForRelation(relationKey string) (*OfferConnection, error) {
   200  	offerConnectionCollection, closer := st.db().GetCollection(offerConnectionsC)
   201  	defer closer()
   202  
   203  	var connDoc offerConnectionDoc
   204  	err := offerConnectionCollection.Find(bson.D{{"relation-key", relationKey}}).One(&connDoc)
   205  	if err == mgo.ErrNotFound {
   206  		return nil, errors.NotFoundf("offer connection for relation %q", relationKey)
   207  	}
   208  	if err != nil {
   209  		return nil, errors.Annotatef(err, "cannot get offer connection details for relation %q", relationKey)
   210  	}
   211  	return newOfferConnection(st, &connDoc), nil
   212  }
   213  
   214  // RemoteConnectionStatus returns summary information about connections to the specified offer.
   215  func (st *State) RemoteConnectionStatus(offerUUID string) (*RemoteConnectionStatus, error) {
   216  	conns, err := st.OfferConnections(offerUUID)
   217  	if err != nil {
   218  		return nil, errors.Trace(err)
   219  	}
   220  	result := &RemoteConnectionStatus{
   221  		totalCount: len(conns),
   222  	}
   223  	for _, conn := range conns {
   224  		key := conn.RelationKey()
   225  		rel, err := st.KeyRelation(key)
   226  		if err != nil {
   227  			// If we can't find the KeyRelation using the conn.RelationKey, then
   228  			// we have a connection that is hanging around that shouldn't be.
   229  			// Unfortunately this isn't the place to die. Instead we should
   230  			// continue on and ignore it, as it's not imperative to the
   231  			// remote connection status.
   232  			//
   233  			// Note: apiserver/facades/client/client/status.go#fetchOffers also
   234  			// performs the same check.
   235  			if errors.IsNotFound(err) {
   236  				logger.Errorf("KeyRelation from offer connection (%s) not found, unable to locate relation key %q.", conn.String(), key)
   237  				continue
   238  			}
   239  			return nil, errors.Trace(err)
   240  		}
   241  		relStatus, err := rel.Status()
   242  		if err != nil {
   243  			return nil, errors.Trace(err)
   244  		}
   245  		if relStatus.Status == status.Joined {
   246  			result.activeCount++
   247  		}
   248  	}
   249  	return result, nil
   250  }
   251  
   252  // RemoteConnectionStatus holds summary information about connections
   253  // to an application offer.
   254  type RemoteConnectionStatus struct {
   255  	activeCount int
   256  	totalCount  int
   257  }
   258  
   259  // TotalConnectionCount returns the number of remote applications
   260  // related to an offer.
   261  func (r *RemoteConnectionStatus) TotalConnectionCount() int {
   262  	return r.totalCount
   263  }
   264  
   265  // ActiveConnectionCount returns the number of active remote applications
   266  // related to an offer.
   267  func (r *RemoteConnectionStatus) ActiveConnectionCount() int {
   268  	return r.activeCount
   269  }