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 }