github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/relation.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "sort" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/juju/charm/v12" 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 21 "github.com/juju/juju/core/leadership" 22 "github.com/juju/juju/core/status" 23 ) 24 25 // relationKey returns a string describing the relation defined by 26 // endpoints, for use in various contexts (including error messages). 27 func relationKey(endpoints []Endpoint) string { 28 eps := epSlice{} 29 for _, ep := range endpoints { 30 eps = append(eps, ep) 31 } 32 sort.Sort(eps) 33 endpointNames := []string{} 34 for _, ep := range eps { 35 endpointNames = append(endpointNames, ep.String()) 36 } 37 return strings.Join(endpointNames, " ") 38 } 39 40 // relationDoc is the internal representation of a Relation in MongoDB. 41 // Note the correspondence with RelationInfo in core/multiwatcher. 42 type relationDoc struct { 43 DocID string `bson:"_id"` 44 Key string `bson:"key"` 45 ModelUUID string `bson:"model-uuid"` 46 Id int `bson:"id"` 47 Endpoints []Endpoint `bson:"endpoints"` 48 Life Life `bson:"life"` 49 UnitCount int `bson:"unitcount"` 50 Suspended bool `bson:"suspended"` 51 SuspendedReason string `bson:"suspended-reason"` 52 } 53 54 // Relation represents a relation between one or two application endpoints. 55 type Relation struct { 56 st *State 57 doc relationDoc 58 } 59 60 func newRelation(st *State, doc *relationDoc) *Relation { 61 return &Relation{ 62 st: st, 63 doc: *doc, 64 } 65 } 66 67 func (r *Relation) String() string { 68 return r.doc.Key 69 } 70 71 // Tag returns a name identifying the relation. 72 func (r *Relation) Tag() names.Tag { 73 return names.NewRelationTag(r.doc.Key) 74 } 75 76 // UnitCount is the number of units still in relation scope. 77 func (r *Relation) UnitCount() int { 78 return r.doc.UnitCount 79 } 80 81 // Suspended returns true if the relation is suspended. 82 func (r *Relation) Suspended() bool { 83 return r.doc.Suspended 84 } 85 86 // SuspendedReason returns the reason why the relation is suspended. 87 func (r *Relation) SuspendedReason() string { 88 return r.doc.SuspendedReason 89 } 90 91 // Refresh refreshes the contents of the relation from the underlying 92 // state. It returns an error that satisfies errors.IsNotFound if the 93 // relation has been removed. 94 func (r *Relation) Refresh() error { 95 relations, closer := r.st.db().GetCollection(relationsC) 96 defer closer() 97 98 doc := relationDoc{} 99 err := relations.FindId(r.doc.DocID).One(&doc) 100 if err == mgo.ErrNotFound { 101 return errors.NotFoundf("relation %v", r) 102 } 103 if err != nil { 104 return errors.Annotatef(err, "cannot refresh relation %v", r) 105 } 106 if r.doc.Id != doc.Id { 107 // The relation has been destroyed and recreated. This is *not* the 108 // same relation; if we pretend it is, we run the risk of violating 109 // the lifecycle-only-advances guarantee. 110 return errors.NotFoundf("relation %v", r) 111 } 112 r.doc = doc 113 return nil 114 } 115 116 // Life returns the relation's current life state. 117 func (r *Relation) Life() Life { 118 return r.doc.Life 119 } 120 121 // Status returns the relation's current status data. 122 func (r *Relation) Status() (status.StatusInfo, error) { 123 rStatus, err := getStatus(r.st.db(), r.globalScope(), "relation") 124 if err != nil { 125 return rStatus, err 126 } 127 return rStatus, nil 128 } 129 130 // SetStatus sets the status of the relation. 131 func (r *Relation) SetStatus(statusInfo status.StatusInfo) error { 132 currentStatus, err := r.Status() 133 if err != nil { 134 return errors.Trace(err) 135 } 136 137 if currentStatus.Status != statusInfo.Status { 138 validTransition := true 139 switch statusInfo.Status { 140 case status.Broken: 141 case status.Suspending: 142 validTransition = currentStatus.Status != status.Broken && currentStatus.Status != status.Suspended 143 case status.Joining: 144 validTransition = currentStatus.Status != status.Broken && currentStatus.Status != status.Joined 145 case status.Joined, status.Suspended: 146 validTransition = currentStatus.Status != status.Broken 147 case status.Error: 148 if statusInfo.Message == "" { 149 return errors.Errorf("cannot set status %q without info", statusInfo.Status) 150 } 151 default: 152 return errors.NewNotValid(nil, fmt.Sprintf("cannot set invalid status %q", statusInfo.Status)) 153 } 154 if !validTransition { 155 return errors.NewNotValid(nil, fmt.Sprintf( 156 "cannot set status %q when relation has status %q", statusInfo.Status, currentStatus.Status)) 157 } 158 } 159 return setStatus(r.st.db(), setStatusParams{ 160 badge: "relation", 161 globalKey: r.globalScope(), 162 status: statusInfo.Status, 163 message: statusInfo.Message, 164 rawData: statusInfo.Data, 165 updated: timeOrNow(statusInfo.Since, r.st.clock()), 166 }) 167 } 168 169 // SetSuspended sets whether the relation is suspended. 170 func (r *Relation) SetSuspended(suspended bool, suspendedReason string) error { 171 if r.doc.Suspended == suspended { 172 return nil 173 } 174 if !suspended && suspendedReason != "" { 175 return errors.New("cannot set suspended reason if not suspended") 176 } 177 178 var buildTxn jujutxn.TransactionSource = func(attempt int) ([]txn.Op, error) { 179 180 if attempt > 1 { 181 if err := r.Refresh(); err != nil { 182 return nil, errors.Trace(err) 183 } 184 } 185 return []txn.Op{{ 186 C: relationsC, 187 Id: r.doc.DocID, 188 Assert: bson.D{{"suspended", r.doc.Suspended}}, 189 Update: bson.D{ 190 {"$set", bson.D{{"suspended", suspended}}}, 191 {"$set", bson.D{{"suspended-reason", suspendedReason}}}, 192 }, 193 }}, nil 194 } 195 196 err := r.st.db().Run(buildTxn) 197 if err == nil { 198 r.doc.Suspended = suspended 199 } 200 return err 201 } 202 203 // DestroyOperation returns a model operation that will allow relation to leave scope. 204 func (r *Relation) DestroyOperation(force bool) *DestroyRelationOperation { 205 return &DestroyRelationOperation{ 206 r: &Relation{r.st, r.doc}, 207 ForcedOperation: ForcedOperation{Force: force}, 208 } 209 } 210 211 // DestroyRelationOperation is a model operation destroy relation. 212 type DestroyRelationOperation struct { 213 // ForcedOperation stores needed information to force this operation. 214 ForcedOperation 215 216 // r holds the relation to destroy. 217 r *Relation 218 } 219 220 // Build is part of the ModelOperation interface. 221 func (op *DestroyRelationOperation) Build(attempt int) ([]txn.Op, error) { 222 if attempt > 0 { 223 if err := op.r.Refresh(); errors.IsNotFound(err) { 224 return nil, jujutxn.ErrNoOperations 225 } else if err != nil { 226 return nil, err 227 } 228 } 229 // When 'force' is set on the operation, this call will return needed operations 230 // and accumulate all operational errors encountered in the operation. 231 // If the 'force' is not set, any error will be fatal and no operations will be returned. 232 switch ops, err := op.internalDestroy(); err { 233 case errRefresh: 234 case errAlreadyDying: 235 return nil, jujutxn.ErrNoOperations 236 case nil: 237 return ops, nil 238 default: 239 if op.Force { 240 logger.Warningf("force destroying relation %v despite error %v", op.r, err) 241 return ops, nil 242 } 243 return nil, err 244 } 245 return nil, jujutxn.ErrNoOperations 246 } 247 248 // Done is part of the ModelOperation interface. 249 func (op *DestroyRelationOperation) Done(err error) error { 250 if err != nil { 251 if !op.Force { 252 return errors.Annotatef(err, "cannot destroy relation %q", op.r) 253 } 254 op.AddError(errors.Errorf("forcefully destroying relation %v proceeded despite encountering ERROR %v", op.r, err)) 255 } 256 return nil 257 } 258 259 // DestroyWithForce may force the destruction of the relation. 260 // In addition, this function also returns all non-fatal operational errors 261 // encountered. 262 func (r *Relation) DestroyWithForce(force bool, maxWait time.Duration) ([]error, error) { 263 op := r.DestroyOperation(force) 264 op.MaxWait = maxWait 265 err := r.st.ApplyOperation(op) 266 return op.Errors, err 267 } 268 269 // Destroy ensures that the relation will be removed at some point; if no units 270 // are currently in scope, it will be removed immediately. 271 func (r *Relation) Destroy() error { 272 errs, err := r.DestroyWithForce(false, time.Duration(0)) 273 if len(errs) != 0 { 274 logger.Warningf("operational errors removing relation %v: %v", r.Id(), errs) 275 } 276 return err 277 } 278 279 // destroyCrossModelRelationUnitsOps returns the operations necessary for the units 280 // of the specified remote application to leave scope. 281 func destroyCrossModelRelationUnitsOps(op *ForcedOperation, remoteApp *RemoteApplication, rel *Relation, onlyForTerminated bool) ([]txn.Op, error) { 282 if onlyForTerminated { 283 statusInfo, err := remoteApp.Status() 284 if op.FatalError(err) && !errors.IsNotFound(err) { 285 return nil, errors.Trace(err) 286 } 287 if err != nil || statusInfo.Status != status.Terminated { 288 return nil, jujutxn.ErrNoOperations 289 } 290 } 291 logger.Debugf("forcing cleanup of units for %v", remoteApp.Name()) 292 remoteUnits, err := rel.AllRemoteUnits(remoteApp.Name()) 293 if op.FatalError(err) { 294 return nil, errors.Trace(err) 295 } else if err != nil { 296 logger.Warningf("could not get remote units for %q: %v", remoteApp.Name(), err) 297 } 298 299 var ops []txn.Op 300 logger.Debugf("got %v relation units to clean", len(remoteUnits)) 301 failRemoteUnits := false 302 for _, ru := range remoteUnits { 303 leaveScopeOps, err := ru.leaveScopeForcedOps(op) 304 if err != nil && err != jujutxn.ErrNoOperations { 305 op.AddError(err) 306 failRemoteUnits = true 307 } 308 ops = append(ops, leaveScopeOps...) 309 } 310 if !op.Force && failRemoteUnits { 311 return nil, errors.Trace(op.LastError()) 312 } 313 return ops, nil 314 } 315 316 // When 'force' is set, this call will construct and apply needed operations 317 // as well as accumulate all operational errors encountered. 318 // If the 'force' is not set, any error will be fatal and no operations will be applied. 319 func (op *DestroyRelationOperation) internalDestroy() (ops []txn.Op, err error) { 320 if len(op.r.doc.Endpoints) == 1 && op.r.doc.Endpoints[0].Role == charm.RolePeer { 321 return nil, errors.Errorf("is a peer relation") 322 } 323 defer func() { 324 if err == nil { 325 // This is a white lie; the document might actually be removed. 326 op.r.doc.Life = Dying 327 } 328 }() 329 rel := &Relation{op.r.st, op.r.doc} 330 331 remoteApp, isCrossModel, err := op.r.RemoteApplication() 332 if op.FatalError(err) { 333 return nil, errors.Trace(err) 334 } else if isCrossModel { 335 // If the status of the consumed app is terminated, we will never 336 // get an orderly exit of units from scope so force the issue. 337 // TODO(wallyworld) - this should be in a force cleanup job after giving things a change to complete normally. 338 destroyOps, err := destroyCrossModelRelationUnitsOps(&op.ForcedOperation, remoteApp, op.r, !op.Force) 339 if err != nil && err != jujutxn.ErrNoOperations { 340 return nil, errors.Trace(err) 341 } 342 ops = append(ops, destroyOps...) 343 // On the offering side, if this is the last relation to the consumer proxy, we may need to remove it also, 344 // but only if the unit count is already 0. This is just a backstop to allow force to fully clean up; normally 345 // unit count is not 0 so this doesn't get run. 346 if remoteApp.IsConsumerProxy() && remoteApp.doc.RelationCount <= 1 && (op.Force || rel.doc.UnitCount == 0) { 347 logger.Debugf("removing cross model consumer proxy and last relation %d: %v", op.r.doc.Id, op.r.doc.Key) 348 removeRelOps, err := rel.removeOps("", "", &op.ForcedOperation) 349 if err != nil && err != jujutxn.ErrNoOperations { 350 return nil, errors.Trace(err) 351 } 352 ops = append(ops, removeRelOps...) 353 var hasLastRefs bson.D 354 if !op.Force { 355 hasLastRefs = bson.D{{"life", remoteApp.doc.Life}, {"relationcount", remoteApp.doc.RelationCount}} 356 } 357 removeAppOps, err := remoteApp.removeOps(hasLastRefs) 358 if err != nil { 359 return nil, errors.Trace(err) 360 } 361 return append(ops, removeAppOps...), nil 362 } 363 } 364 365 // In this context, aborted transactions indicate that the number of units 366 // in scope have changed between 0 and not-0. The chances of 5 successive 367 // attempts each hitting this change -- which is itself an unlikely one -- 368 // are considered to be extremely small. 369 // When 'force' is set, this call will return needed operations 370 // and accumulate all operational errors encountered in the operation. 371 // If the 'force' is not set, any error will be fatal and no operations will be returned. 372 destroyOps, _, err := rel.destroyOps("", &op.ForcedOperation) 373 if err == errAlreadyDying { 374 return nil, jujutxn.ErrNoOperations 375 } else if op.FatalError(err) { 376 return nil, err 377 } 378 return append(ops, destroyOps...), nil 379 } 380 381 // destroyOps returns the operations necessary to destroy the relation, and 382 // whether those operations will lead to the relation's removal. These 383 // operations may include changes to the relation's applications; however, if 384 // ignoreApplication is not empty, no operations modifying that application will 385 // be generated. 386 // When 'force' is set, this call will return both operations to remove this 387 // relation as well as all operational errors encountered. 388 // If the 'force' is not set, any error will be fatal and no operations will be returned. 389 func (r *Relation) destroyOps(ignoreApplication string, op *ForcedOperation) (ops []txn.Op, isRemove bool, err error) { 390 if r.doc.Life != Alive { 391 if !op.Force { 392 return nil, false, errAlreadyDying 393 } 394 } 395 scopes, closer := r.st.db().GetCollection(relationScopesC) 396 defer closer() 397 sel := bson.M{"_id": bson.M{ 398 "$regex": fmt.Sprintf("^%s#", r.st.docID(r.globalScope())), 399 }} 400 unitsInScope, err := scopes.Find(sel).Count() 401 if err != nil { 402 return nil, false, errors.Trace(err) 403 } 404 if unitsInScope != r.doc.UnitCount { 405 if op.Force { 406 logger.Warningf("ignoring unit count mismatch on relation %v: expected %d units in scope but got %d", r, r.doc.UnitCount, unitsInScope) 407 } else { 408 return nil, false, errors.Errorf("unit count mismatch on relation %v: expected %d units in scope but got %d", r, r.doc.UnitCount, unitsInScope) 409 } 410 } 411 412 if r.doc.UnitCount == 0 || unitsInScope == 0 { 413 // When 'force' is set, this call will return both needed operations 414 // as well as all operational errors encountered. 415 // If the 'force' is not set, any error will be fatal and no operations will be returned. 416 removeOps, err := r.removeOps(ignoreApplication, "", op) 417 if err != nil { 418 if !op.Force { 419 return nil, false, err 420 } 421 logger.Warningf("ignoring error (%v) while constructing relation %v destroy operations since force is used", err, r) 422 } 423 return removeOps, true, nil 424 } 425 426 lifeAssert := isAliveDoc 427 if op.Force { 428 // Since we are force destroying, life assert should be current relation's life. 429 lifeAssert = bson.D{{"life", r.doc.Life}} 430 deadline := r.st.stateClock.Now().Add(op.MaxWait) 431 ops = append(ops, newCleanupAtOp(deadline, cleanupForceDestroyedRelation, strconv.Itoa(r.Id()))) 432 } 433 434 ops = append(ops, txn.Op{ 435 C: relationsC, 436 Id: r.doc.DocID, 437 Assert: append(bson.D{{"unitcount", bson.D{{"$gt", 0}}}}, lifeAssert...), 438 Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, 439 }) 440 return ops, false, nil 441 } 442 443 // removeOps returns the operations necessary to remove the relation. If 444 // ignoreApplication is not empty, no operations affecting that application will be 445 // included; if departingUnitName is non-empty, this implies that the 446 // relation's applications may be Dying and otherwise unreferenced, and may thus 447 // require removal themselves. 448 // When 'force' is set, this call will return needed operations 449 // and accumulate all operational errors encountered in the operation. 450 // If the 'force' is not set, any error will be fatal and no operations will be returned. 451 func (r *Relation) removeOps(ignoreApplication string, departingUnitName string, op *ForcedOperation) ([]txn.Op, error) { 452 relOp := txn.Op{ 453 C: relationsC, 454 Id: r.doc.DocID, 455 Remove: true, 456 } 457 if op.Force { 458 // There can be a mismatch between the unit count recorded on a relation and the actual number 459 // of units in scope if a multi-controller cross model relation is removed and the controllers 460 // can't talk to coordinate cleanup. 461 relOp.Assert = bson.D{{"life", r.doc.Life}, {"unitcount", r.doc.UnitCount}} 462 } else { 463 if departingUnitName != "" { 464 relOp.Assert = bson.D{{"life", Dying}, {"unitcount", 1}} 465 } else { 466 relOp.Assert = bson.D{{"life", Alive}, {"unitcount", 0}} 467 } 468 } 469 ops := []txn.Op{relOp} 470 for _, ep := range r.doc.Endpoints { 471 if ep.ApplicationName == ignoreApplication { 472 continue 473 } 474 app, err := applicationByName(r.st, ep.ApplicationName) 475 if err != nil { 476 op.AddError(err) 477 } else { 478 if app.IsRemote() { 479 epOps, err := r.removeRemoteEndpointOps(ep, departingUnitName != "") 480 if err != nil { 481 op.AddError(err) 482 } 483 ops = append(ops, epOps...) 484 } else { 485 // When 'force' is set, this call will return both needed operations 486 // as well as all operational errors encountered. 487 // If the 'force' is not set, any error will be fatal and no operations will be returned. 488 epOps, err := r.removeLocalEndpointOps(ep, departingUnitName, op) 489 if err != nil { 490 op.AddError(err) 491 } 492 ops = append(ops, epOps...) 493 } 494 } 495 } 496 ops = append(ops, removeStatusOp(r.st, r.globalScope())) 497 ops = append(ops, removeRelationNetworksOps(r.st, r.doc.Key)...) 498 re := r.st.RemoteEntities() 499 tokenOps := re.removeRemoteEntityOps(r.Tag()) 500 ops = append(ops, tokenOps...) 501 offerOps := removeOfferConnectionsForRelationOps(r.Id()) 502 ops = append(ops, offerOps...) 503 secretPermissionsOps, err := r.st.removeScopedSecretPermissionOps(r.Tag()) 504 if err != nil { 505 return nil, errors.Trace(err) 506 } 507 ops = append(ops, secretPermissionsOps...) 508 // This cleanup does not need to be forced. 509 cleanupOp := newCleanupOp(cleanupRelationSettings, fmt.Sprintf("r#%d#", r.Id())) 510 return append(ops, cleanupOp), nil 511 } 512 513 // When 'force' is set, this call will return both needed operations 514 // as well as all operational errors encountered. 515 // If the 'force' is not set, any error will be fatal and no operations will be returned. 516 func (r *Relation) removeLocalEndpointOps(ep Endpoint, departingUnitName string, op *ForcedOperation) ([]txn.Op, error) { 517 var asserts bson.D 518 hasRelation := bson.D{{"relationcount", bson.D{{"$gt", 0}}}} 519 departingUnitApplicationMatchesEndpoint := func() bool { 520 s, err := names.UnitApplication(departingUnitName) 521 return err == nil && s == ep.ApplicationName 522 } 523 var cleanupOps []txn.Op 524 if departingUnitName == "" { 525 // We're constructing a destroy operation, either of the relation 526 // or one of its applications, and can therefore be assured that both 527 // applications are Alive. 528 asserts = append(hasRelation, isAliveDoc...) 529 } else if departingUnitApplicationMatchesEndpoint() { 530 // This application must have at least one unit -- the one that's 531 // departing the relation -- so it cannot be ready for removal. 532 cannotDieYet := bson.D{{"unitcount", bson.D{{"$gt", 0}}}} 533 asserts = append(hasRelation, cannotDieYet...) 534 } else { 535 // This application may require immediate removal. 536 // Check if the application is Dying, and if so, queue up a potential 537 // cleanup in case this was the last reference. 538 applications, closer := r.st.db().GetCollection(applicationsC) 539 defer closer() 540 541 asserts = append(hasRelation) 542 var appDoc applicationDoc 543 if err := applications.FindId(ep.ApplicationName).One(&appDoc); err == nil { 544 if appDoc.Life != Alive { 545 cleanupOps = append(cleanupOps, newCleanupOp( 546 cleanupApplication, 547 ep.ApplicationName, 548 false, // destroyStorage 549 op.Force, 550 )) 551 } 552 } else if !op.Force { 553 return nil, errors.Trace(err) 554 } 555 } 556 return append([]txn.Op{{ 557 C: applicationsC, 558 Id: r.st.docID(ep.ApplicationName), 559 Assert: asserts, 560 Update: bson.D{{"$inc", bson.D{{"relationcount", -1}}}}, 561 }}, cleanupOps...), nil 562 } 563 564 func (r *Relation) removeRemoteEndpointOps(ep Endpoint, unitDying bool) ([]txn.Op, error) { 565 var asserts bson.D 566 hasRelation := bson.D{{"relationcount", bson.D{{"$gt", 0}}}} 567 if !unitDying { 568 // We're constructing a destroy operation, either of the relation 569 // or one of its application, and can therefore be assured that both 570 // applications are Alive. 571 asserts = append(hasRelation, isAliveDoc...) 572 } else { 573 // The remote application may require immediate removal if all relations are being 574 // removed and it's either dying or on the offering side as a consumer proxy. 575 applications, closer := r.st.db().GetCollection(remoteApplicationsC) 576 defer closer() 577 578 app := &RemoteApplication{st: r.st} 579 hasLastRef := bson.D{{"relationcount", 1}} 580 shouldRemove := bson.D{{"$or", []bson.D{ 581 {{"life", bson.D{{"$ne", Alive}}}}, 582 {{"is-consumer-proxy", true}}, 583 }}} 584 585 removable := append(bson.D{{"_id", ep.ApplicationName}}, hasLastRef...) 586 removable = append(removable, shouldRemove...) 587 if err := applications.Find(removable).One(&app.doc); err == nil { 588 removeOps, err := app.removeOps(hasLastRef) 589 return removeOps, errors.Trace(err) 590 } else if err != mgo.ErrNotFound { 591 return nil, err 592 } 593 // If not, we must check that this is still the case when the 594 // transaction is applied. 595 asserts = bson.D{{"$or", []bson.D{ 596 {{"life", Alive}}, 597 {{"is-consumer-proxy", false}}, 598 {{"relationcount", bson.D{{"$gt", 1}}}}, 599 }}} 600 } 601 return []txn.Op{{ 602 C: remoteApplicationsC, 603 Id: r.st.docID(ep.ApplicationName), 604 Assert: asserts, 605 Update: bson.D{{"$inc", bson.D{{"relationcount", -1}}}}, 606 }}, nil 607 } 608 609 // Id returns the integer internal relation key. This is exposed 610 // because the unit agent needs to expose a value derived from this 611 // (as JUJU_RELATION_ID) to allow relation hooks to differentiate 612 // between relations with different applications. 613 func (r *Relation) Id() int { 614 return r.doc.Id 615 } 616 617 // Endpoint returns the endpoint of the relation for the named application. 618 // If the application is not part of the relation, an error will be returned. 619 func (r *Relation) Endpoint(applicationname string) (Endpoint, error) { 620 for _, ep := range r.doc.Endpoints { 621 if ep.ApplicationName == applicationname { 622 return ep, nil 623 } 624 } 625 msg := fmt.Sprintf("application %q is not a member of %q", applicationname, r) 626 return Endpoint{}, errors.NewNotFound(nil, msg) 627 } 628 629 // ModelUUID returns the model UUID for the relation. 630 func (r *Relation) ModelUUID() string { 631 return r.doc.ModelUUID 632 } 633 634 // Endpoints returns the endpoints for the relation. 635 func (r *Relation) Endpoints() []Endpoint { 636 return r.doc.Endpoints 637 } 638 639 // RelatedEndpoints returns the endpoints of the relation r with which 640 // units of the named application will establish relations. If the application 641 // is not part of the relation r, an error will be returned. 642 func (r *Relation) RelatedEndpoints(applicationname string) ([]Endpoint, error) { 643 local, err := r.Endpoint(applicationname) 644 if err != nil { 645 return nil, err 646 } 647 role := counterpartRole(local.Role) 648 var eps []Endpoint 649 for _, ep := range r.doc.Endpoints { 650 if ep.Role == role { 651 eps = append(eps, ep) 652 } 653 } 654 if eps == nil { 655 return nil, errors.Errorf("no endpoints of %q relate to application %q", r, applicationname) 656 } 657 return eps, nil 658 } 659 660 // Unit returns a RelationUnit for the supplied unit. 661 func (r *Relation) Unit(u *Unit) (*RelationUnit, error) { 662 const isLocalUnit = true 663 return r.unit(u.Name(), u.doc.Principal, u.IsPrincipal(), isLocalUnit) 664 } 665 666 // RemoteUnit returns a RelationUnit for the supplied unit 667 // of a remote application. 668 func (r *Relation) RemoteUnit(unitName string) (*RelationUnit, error) { 669 // Verify that the unit belongs to a remote application. 670 appName, err := names.UnitApplication(unitName) 671 if err != nil { 672 return nil, errors.Trace(err) 673 } 674 if _, err := r.st.RemoteApplication(appName); err != nil { 675 return nil, errors.Trace(err) 676 } 677 // Only non-subordinate applications may be offered for remote 678 // relation, so all remote units are principals. 679 const principal = "" 680 const isPrincipal = true 681 const isLocalUnit = false 682 return r.unit(unitName, principal, isPrincipal, isLocalUnit) 683 } 684 685 // AllRemoteUnits returns all the RelationUnits for the remote 686 // application units for a given application. 687 func (r *Relation) AllRemoteUnits(appName string) ([]*RelationUnit, error) { 688 // Verify that the unit belongs to a remote application. 689 if _, err := r.st.RemoteApplication(appName); err != nil { 690 return nil, errors.Trace(err) 691 } 692 693 relationScopes, closer := r.st.db().GetCollection(relationScopesC) 694 defer closer() 695 696 ep, err := r.Endpoint(appName) 697 if err != nil { 698 return nil, err 699 } 700 scope := r.globalScope() 701 parts := []string{"^" + scope, string(ep.Role), appName + "/"} 702 ruRegex := strings.Join(parts, "#") 703 704 var docs []relationScopeDoc 705 if err := relationScopes.Find(bson.D{{"key", bson.D{{"$regex", ruRegex}}}}).All(&docs); err != nil { 706 return nil, errors.Trace(err) 707 } 708 result := make([]*RelationUnit, len(docs)) 709 for i, doc := range docs { 710 result[i] = &RelationUnit{ 711 st: r.st, 712 relation: r, 713 unitName: doc.unitName(), 714 isPrincipal: true, 715 isLocalUnit: false, 716 endpoint: ep, 717 scope: scope, 718 } 719 } 720 return result, nil 721 } 722 723 // RemoteApplication returns the remote application if 724 // this relation is a cross-model relation, and a bool 725 // indicating if it cross-model or not. 726 func (r *Relation) RemoteApplication() (*RemoteApplication, bool, error) { 727 for _, ep := range r.Endpoints() { 728 app, err := r.st.RemoteApplication(ep.ApplicationName) 729 if err == nil { 730 return app, true, nil 731 } else if !errors.IsNotFound(err) { 732 return nil, false, errors.Trace(err) 733 } 734 } 735 return nil, false, nil 736 } 737 738 func (r *Relation) unit( 739 unitName string, 740 principal string, 741 isPrincipal bool, 742 isLocalUnit bool, 743 ) (*RelationUnit, error) { 744 appName, err := names.UnitApplication(unitName) 745 if err != nil { 746 return nil, err 747 } 748 ep, err := r.Endpoint(appName) 749 if err != nil { 750 return nil, err 751 } 752 scope := r.globalScope() 753 if ep.Scope == charm.ScopeContainer { 754 container := principal 755 if container == "" { 756 container = unitName 757 } 758 scope = fmt.Sprintf("%s#%s", scope, container) 759 } 760 return &RelationUnit{ 761 st: r.st, 762 relation: r, 763 unitName: unitName, 764 isPrincipal: isPrincipal, 765 isLocalUnit: isLocalUnit, 766 endpoint: ep, 767 scope: scope, 768 }, nil 769 } 770 771 // globalScope returns the scope prefix for relation scope document keys 772 // in the global scope. 773 func (r *Relation) globalScope() string { 774 return relationGlobalScope(r.doc.Id) 775 } 776 777 func relationGlobalScope(id int) string { 778 return fmt.Sprintf("r#%d", id) 779 } 780 781 // relationSettingsCleanupChange removes the settings doc. 782 type relationSettingsCleanupChange struct { 783 Prefix string 784 } 785 786 // Prepare is part of the Change interface. 787 func (change relationSettingsCleanupChange) Prepare(db Database) ([]txn.Op, error) { 788 settings, closer := db.GetCollection(settingsC) 789 defer closer() 790 sel := bson.D{{"_id", bson.D{{"$regex", "^" + change.Prefix}}}} 791 var docs []struct { 792 DocID string `bson:"_id"` 793 } 794 err := settings.Find(sel).Select(bson.D{{"_id", 1}}).All(&docs) 795 if err != nil { 796 return nil, errors.Trace(err) 797 } 798 if len(docs) == 0 { 799 return nil, ErrChangeComplete 800 } 801 802 ops := make([]txn.Op, len(docs)) 803 for i, doc := range docs { 804 ops[i] = txn.Op{ 805 C: settingsC, 806 Id: doc.DocID, 807 Remove: true, 808 } 809 } 810 return ops, nil 811 812 } 813 814 func relationApplicationSettingsKey(id int, application string) string { 815 return fmt.Sprintf("%s#%s", relationGlobalScope(id), application) 816 } 817 818 // ApplicationSettings returns the application-level settings for the 819 // specified application in this relation. 820 func (r *Relation) ApplicationSettings(appName string) (map[string]interface{}, error) { 821 ep, err := r.Endpoint(appName) 822 if err != nil { 823 return nil, errors.Trace(err) 824 } 825 applicationKey := relationApplicationSettingsKey(r.Id(), ep.ApplicationName) 826 s, err := readSettings(r.st.db(), settingsC, applicationKey) 827 if err != nil { 828 return nil, errors.Annotatef(err, "relation %q application %q", r.String(), appName) 829 } 830 return s.Map(), nil 831 } 832 833 // UpdateApplicationSettings updates the given application's settings 834 // in this relation. It requires a current leadership token. 835 func (r *Relation) UpdateApplicationSettings(appName string, token leadership.Token, updates map[string]interface{}) error { 836 modelOp, err := r.UpdateApplicationSettingsOperation(appName, token, updates) 837 if err != nil { 838 return errors.Trace(err) 839 } 840 841 err = r.st.ApplyOperation(modelOp) 842 if errors.IsNotFound(err) { 843 return errors.NotFoundf("relation %q application %q", r, appName) 844 } else if err != nil { 845 return errors.Annotatef(err, "relation %q application %q", r, appName) 846 } 847 return nil 848 } 849 850 // UpdateApplicationSettingsOperation returns a ModelOperation for updating the 851 // given application's settings in this relation. It requires a current 852 // leadership token. 853 func (r *Relation) UpdateApplicationSettingsOperation(appName string, token leadership.Token, updates map[string]interface{}) (ModelOperation, error) { 854 // We can calculate the actual update ahead of time; it's not dependent 855 // upon the current state of the document. (*Writing* it should depend 856 // on document state, but that's handled below.) 857 ep, err := r.Endpoint(appName) 858 if err != nil { 859 return nil, errors.Trace(err) 860 } 861 862 key := relationApplicationSettingsKey(r.Id(), ep.ApplicationName) 863 return newUpdateLeaderSettingsOperation(r.st.db(), token, key, updates), nil 864 } 865 866 // WatchApplicationSettings returns a notify watcher that will signal 867 // whenever the specified application's relation settings are changed. 868 func (r *Relation) WatchApplicationSettings(app *Application) (NotifyWatcher, error) { 869 ep, err := r.Endpoint(app.Name()) 870 if err != nil { 871 return nil, errors.Trace(err) 872 } 873 key := relationApplicationSettingsKey(r.Id(), ep.ApplicationName) 874 watcher := newEntityWatcher(r.st, settingsC, r.st.docID(key)) 875 return watcher, nil 876 }