github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/relationunit.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "strings" 9 "time" 10 11 "github.com/juju/charm/v12" 12 "github.com/juju/collections/set" 13 "github.com/juju/errors" 14 "github.com/juju/mgo/v3" 15 "github.com/juju/mgo/v3/bson" 16 "github.com/juju/mgo/v3/txn" 17 "github.com/juju/names/v5" 18 jujutxn "github.com/juju/txn/v3" 19 "github.com/kr/pretty" 20 21 stateerrors "github.com/juju/juju/state/errors" 22 ) 23 24 var rulogger = logger.Child("relationunit") 25 26 // RelationUnit holds information about a single unit in a relation, and 27 // allows clients to conveniently access unit-specific functionality. 28 type RelationUnit struct { 29 st *State 30 relation *Relation 31 unitName string 32 isPrincipal bool 33 endpoint Endpoint 34 scope string 35 36 // isLocalUnit is true for relation units representing 37 // the local side of a cross model relation, or for 38 // any 2 units in a non cross model relation. 39 isLocalUnit bool 40 } 41 42 // Relation returns the relation associated with the unit. 43 func (ru *RelationUnit) Relation() *Relation { 44 return ru.relation 45 } 46 47 // Endpoint returns the relation endpoint that defines the unit's 48 // participation in the relation. 49 func (ru *RelationUnit) Endpoint() Endpoint { 50 return ru.endpoint 51 } 52 53 // UnitName returns the name of the unit in the relation. 54 func (ru *RelationUnit) UnitName() string { 55 return ru.unitName 56 } 57 58 // EnterScope ensures that the unit has entered its scope in the relation. 59 // When the unit has already entered its relation scope, EnterScope will report 60 // success but make no changes to state. 61 // 62 // Otherwise, assuming both the relation and the unit are alive, it will enter 63 // scope and create or overwrite the unit's settings in the relation according 64 // to the supplied map. 65 // 66 // If the unit is a principal and the relation has container scope, EnterScope 67 // will also create the required subordinate unit, if it does not already exist; 68 // this is because there's no point having a principal in scope if there is no 69 // corresponding subordinate to join it. 70 // 71 // Once a unit has entered a scope, it stays in scope without further 72 // intervention; the relation will not be able to become Dead until all units 73 // have departed its scopes. 74 func (ru *RelationUnit) EnterScope(settings map[string]interface{}) error { 75 db, dbCloser := ru.st.newDB() 76 defer dbCloser() 77 relationScopes, rsCloser := db.GetCollection(relationScopesC) 78 defer rsCloser() 79 ruKey := ru.key() 80 relationDocID := ru.relation.doc.DocID 81 82 var settingsChanged func() (bool, error) 83 var existingSubName string 84 prefix := fmt.Sprintf("unit %q in relation %q: ", ru.unitName, ru.relation) 85 86 buildTxn := func(attempt int) ([]txn.Op, error) { 87 // Before retrying the transaction, check the following 88 // assertions: 89 if attempt > 0 { 90 if count, err := relationScopes.FindId(ruKey).Count(); err != nil { 91 return nil, errors.Trace(err) 92 } else if count != 0 { 93 // The scope document exists, so we're actually already in scope. 94 return nil, nil 95 } 96 97 // The relation or unit might no longer be Alive. (Note that there is no 98 // need for additional checks if we're trying to create a subordinate 99 // unit: this could fail due to the subordinate applications not being Alive, 100 // but this case will always be caught by the check for the relation's 101 // life (because a relation cannot be Alive if its applications are not).) 102 relations, rCloser := db.GetCollection(relationsC) 103 defer rCloser() 104 if alive, err := isAliveWithSession(relations, relationDocID); err != nil { 105 return nil, errors.Trace(err) 106 } else if !alive { 107 return nil, errors.Annotate(stateerrors.ErrCannotEnterScope, prefix+"relation is no longer alive") 108 } 109 if ru.isLocalUnit { 110 units, uCloser := db.GetCollection(unitsC) 111 defer uCloser() 112 if alive, err := isAliveWithSession(units, ru.unitName); err != nil { 113 return nil, errors.Trace(err) 114 } else if !alive { 115 return nil, errors.Annotate(stateerrors.ErrCannotEnterScope, prefix+"unit is no longer alive") 116 117 } 118 119 // Maybe a subordinate used to exist, but is no longer alive. If that is 120 // case, we will be unable to enter scope until that unit is gone. 121 if existingSubName != "" { 122 if alive, err := isAliveWithSession(units, existingSubName); err != nil { 123 return nil, errors.Trace(err) 124 } else if !alive { 125 return nil, errors.Annotatef(stateerrors.ErrCannotEnterScopeYet, prefix+"subordinate %v is no longer alive", existingSubName) 126 } 127 } 128 } 129 130 // It's possible that there was a pre-existing settings doc whose version 131 // has changed under our feet, preventing us from clearing it properly; if 132 // that is the case, something is seriously wrong (nobody else should be 133 // touching that doc under our feet) and we should bail out. 134 if changed, err := settingsChanged(); err != nil { 135 return nil, errors.Trace(err) 136 } else if changed { 137 return nil, fmt.Errorf(prefix + "concurrent settings change detected") 138 } 139 } 140 141 // Verify that the unit is not already in scope, and exit without error 142 // if it is. 143 if count, err := relationScopes.FindId(ruKey).Count(); err != nil { 144 return nil, errors.Trace(err) 145 } else if count != 0 { 146 return nil, nil 147 } 148 149 // Collect the operations necessary to enter scope, as follows: 150 // * Check unit and relation state, and incref the relation. 151 // * TODO(fwereade): check unit status == params.StatusActive (this 152 // breaks a bunch of tests in a boring but noisy-to-fix way, and is 153 // being saved for a followup). 154 var ops []txn.Op 155 if ru.isLocalUnit { 156 ops = append(ops, txn.Op{ 157 C: unitsC, 158 Id: ru.unitName, 159 Assert: isAliveDoc, 160 }) 161 } 162 ops = append(ops, txn.Op{ 163 C: relationsC, 164 Id: relationDocID, 165 Assert: isAliveDoc, 166 Update: bson.D{{"$inc", bson.D{{"unitcount", 1}}}}, 167 }) 168 169 // * Create the unit settings in this relation, if they do not already 170 // exist; or completely overwrite them if they do. This must happen 171 // before we create the scope doc, because the existence of a scope doc 172 // is considered to be a guarantee of the existence of a settings doc. 173 settingsColl, sCloser := db.GetCollection(settingsC) 174 defer sCloser() 175 if count, err := settingsColl.FindId(ruKey).Count(); err != nil { 176 return nil, errors.Trace(err) 177 } else if count == 0 { 178 ops = append(ops, createSettingsOp(settingsC, ruKey, settings)) 179 settingsChanged = func() (bool, error) { return false, nil } 180 } else { 181 var rop txn.Op 182 rop, settingsChanged, err = replaceSettingsOp(ru.st.db(), settingsC, ruKey, settings) 183 if err != nil { 184 return nil, errors.Trace(err) 185 } 186 ops = append(ops, rop) 187 } 188 189 // * Create the scope doc. 190 ops = append(ops, txn.Op{ 191 C: relationScopesC, 192 Id: ruKey, 193 Assert: txn.DocMissing, 194 Insert: relationScopeDoc{ 195 Key: ruKey, 196 }, 197 }) 198 199 // * If the unit should have a subordinate, and does not, create it. 200 if subOps, subName, err := ru.subordinateOps(); err != nil { 201 return nil, errors.Trace(err) 202 } else { 203 existingSubName = subName 204 ops = append(ops, subOps...) 205 } 206 return ops, nil 207 } 208 209 // Now run the complete transaction. 210 return ru.st.db().Run(buildTxn) 211 } 212 213 // CounterpartApplications returns the slice of application names that are the counterpart of this unit. 214 // (So for Peer relations, app is returned, for a Provider the apps on Requirer side is returned 215 func (ru *RelationUnit) CounterpartApplications() []string { 216 counterApps := set.NewStrings() 217 counterRole := counterpartRole(ru.endpoint.Role) 218 for _, ep := range ru.relation.Endpoints() { 219 if ep.Role == counterRole { 220 counterApps.Add(ep.ApplicationName) 221 } 222 } 223 return counterApps.SortedValues() 224 } 225 226 // counterpartApplicationSettingsKeys is the database keys of related applications. 227 func (ru *RelationUnit) counterpartApplicationSettingsKeys() []string { 228 counterpartApps := ru.CounterpartApplications() 229 out := make([]string, len(counterpartApps)) 230 for i, appName := range counterpartApps { 231 out[i] = relationApplicationSettingsKey(ru.relation.Id(), appName) 232 } 233 return out 234 } 235 236 // subordinateOps returns any txn operations necessary to ensure sane 237 // subordinate state when entering scope. If a required subordinate unit 238 // exists and is Alive, its name will be returned as well; if one exists 239 // but is not Alive, ErrCannotEnterScopeYet is returned. 240 func (ru *RelationUnit) subordinateOps() ([]txn.Op, string, error) { 241 units, closer := ru.st.db().GetCollection(unitsC) 242 defer closer() 243 244 if !ru.isPrincipal || ru.endpoint.Scope != charm.ScopeContainer { 245 return nil, "", nil 246 } 247 related, err := ru.relation.RelatedEndpoints(ru.endpoint.ApplicationName) 248 if err != nil { 249 return nil, "", err 250 } 251 if len(related) != 1 { 252 return nil, "", errors.Errorf("expected single related endpoint, got %v", related) 253 } 254 // Find the machine ID that the principal unit is deployed on, and use 255 // that for the subordinate. It is worthwhile noting that if the unit is 256 // in a CAAS model, there are no machines. 257 principal, err := ru.st.Unit(ru.unitName) 258 if err != nil { 259 return nil, "", errors.Annotate(err, "unable to load principal unit") 260 } 261 var principalMachineID string 262 if principal.ShouldBeAssigned() { 263 // We don't care just now if the machine isn't assigned, as CAAS models 264 // will return that error. For IAAS models, the machine *should* always 265 // be assigned before it is able to enter scope. 266 // We don't check the error here now because it'll cause *huge* test 267 // fallout as many tests don't follow reality, particularly when 268 // relations are being tested. 269 principalMachineID, _ = principal.AssignedMachineId() 270 } 271 272 applicationname, unitName := related[0].ApplicationName, ru.unitName 273 selSubordinate := bson.D{{"application", applicationname}, {"principal", unitName}} 274 var lDoc lifeDoc 275 if err := units.Find(selSubordinate).One(&lDoc); err == mgo.ErrNotFound { 276 application, err := ru.st.Application(applicationname) 277 if err != nil { 278 return nil, "", err 279 } 280 _, ops, err := application.addUnitOps(unitName, AddUnitParams{ 281 machineID: principalMachineID, 282 }, nil) 283 return ops, "", err 284 } else if err != nil { 285 return nil, "", err 286 } else if lDoc.Life != Alive { 287 return nil, "", stateerrors.ErrCannotEnterScopeYet 288 } 289 return []txn.Op{{ 290 C: unitsC, 291 Id: lDoc.Id, 292 Assert: isAliveDoc, 293 }}, lDoc.Id, nil 294 } 295 296 // PrepareLeaveScope causes the unit to be reported as departed by watchers, 297 // but does not *actually* leave the scope, to avoid triggering relation 298 // cleanup. 299 func (ru *RelationUnit) PrepareLeaveScope() error { 300 relationScopes, closer := ru.st.db().GetCollection(relationScopesC) 301 defer closer() 302 303 key := ru.key() 304 if count, err := relationScopes.FindId(key).Count(); err != nil { 305 return err 306 } else if count == 0 { 307 return nil 308 } 309 ops := []txn.Op{{ 310 C: relationScopesC, 311 Id: key, 312 Update: bson.D{{"$set", bson.D{{"departing", true}}}}, 313 }} 314 return ru.st.db().RunTransaction(ops) 315 } 316 317 // LeaveScopeOperation returns a model operation that will allow relation to leave scope. 318 func (ru *RelationUnit) LeaveScopeOperation(force bool) *LeaveScopeOperation { 319 return &LeaveScopeOperation{ 320 ru: &RelationUnit{ 321 st: ru.st, 322 relation: ru.relation, 323 unitName: ru.unitName, 324 isPrincipal: ru.isPrincipal, 325 endpoint: ru.endpoint, 326 scope: ru.scope, 327 isLocalUnit: ru.isLocalUnit, 328 }, 329 ForcedOperation: ForcedOperation{Force: force}, 330 } 331 } 332 333 // LeaveScopeOperation is a model operation for relation to leave scope. 334 type LeaveScopeOperation struct { 335 // ForcedOperation stores needed information to force this operation. 336 ForcedOperation 337 338 // ru holds the unit relation that wants to leave scope. 339 ru *RelationUnit 340 } 341 342 // Build is part of the ModelOperation interface. 343 func (op *LeaveScopeOperation) Build(attempt int) ([]txn.Op, error) { 344 rulogger.Tracef("%v attempt %d to leave scope", op.Description(), attempt+1) 345 if attempt > 0 { 346 if err := op.ru.relation.Refresh(); errors.IsNotFound(err) { 347 return nil, jujutxn.ErrNoOperations 348 } else if err != nil { 349 return nil, err 350 } 351 } 352 // When 'force' is set on the operation, this call will return needed operations 353 // and accumulate all operational errors encountered in the operation. 354 // If the 'force' is not set, any error will be fatal and no operations will be returned. 355 switch ops, err := op.internalLeaveScope(); err { 356 case errRefresh: 357 case errAlreadyDying: 358 return nil, jujutxn.ErrNoOperations 359 case nil: 360 return ops, nil 361 default: 362 if op.Force { 363 rulogger.Warningf("forcing %v to leave scope despite error %v", op.Description(), err) 364 return ops, nil 365 } 366 return nil, err 367 } 368 return nil, jujutxn.ErrNoOperations 369 } 370 371 func (op *LeaveScopeOperation) Description() string { 372 return fmt.Sprintf("unit %q in relation %q", op.ru.unitName, op.ru.relation) 373 } 374 375 // Done is part of the ModelOperation interface. 376 func (op *LeaveScopeOperation) Done(err error) error { 377 if err != nil { 378 if !op.Force { 379 return errors.Annotatef(err, "%v cannot leave scope", op.Description()) 380 } 381 op.AddError(errors.Errorf("%v tried to forcefully leave scope but proceeded despite encountering ERROR %v", op.Description(), err)) 382 } 383 return nil 384 } 385 386 // LeaveScopeWithForce in addition to doing what LeaveScope() does, 387 // when force is passed in as 'true', forces relation unit to leave scope, 388 // ignoring errors. 389 func (ru *RelationUnit) LeaveScopeWithForce(force bool, maxWait time.Duration) ([]error, error) { 390 op := ru.LeaveScopeOperation(force) 391 op.MaxWait = maxWait 392 err := ru.st.ApplyOperation(op) 393 return op.Errors, err 394 } 395 396 // LeaveScope signals that the unit has left its scope in the relation. 397 // After the unit has left its relation scope, it is no longer a member 398 // of the relation; if the relation is dying when its last member unit 399 // leaves, it is removed immediately. It is not an error to leave a scope 400 // that the unit is not, or never was, a member of. 401 func (ru *RelationUnit) LeaveScope() error { 402 errs, err := ru.LeaveScopeWithForce(false, time.Duration(0)) 403 if len(errs) != 0 { 404 rulogger.Warningf("operational errors leaving scope for unit %q in relation %q: %v", ru.unitName, ru.relation, errs) 405 } 406 return err 407 } 408 409 // leaveScopeForcedOps is an internal method used by other state objects when they just want 410 // to get database operations that are involved in leaving scope without 411 // the actual immediate act of leaving scope. 412 func (ru *RelationUnit) leaveScopeForcedOps(existingOperation *ForcedOperation) ([]txn.Op, error) { 413 // It does not matter that we are say false to force here- we'll overwrite the whole ForcedOperation. 414 leaveScopeOperation := ru.LeaveScopeOperation(false) 415 leaveScopeOperation.ForcedOperation = *existingOperation 416 return leaveScopeOperation.internalLeaveScope() 417 } 418 419 // When 'force' is set, this call will return needed operations 420 // and will accumulate all operational errors encountered in the operation. 421 // If the 'force' is not set, any error will be fatal and no operations will be applied. 422 func (op *LeaveScopeOperation) internalLeaveScope() ([]txn.Op, error) { 423 relationScopes, closer := op.ru.st.db().GetCollection(relationScopesC) 424 defer closer() 425 426 key := op.ru.key() 427 // The logic below is involved because we remove a dying relation 428 // with the last unit that leaves a scope in it. It handles three 429 // possible cases: 430 // 431 // 1. Relation is alive: just leave the scope. 432 // 433 // 2. Relation is dying, and other units remain: just leave the scope. 434 // 435 // 3. Relation is dying, and this is the last unit: leave the scope 436 // and remove the relation. 437 // 438 // In each of those cases, proper assertions are done to guarantee 439 // that the condition observed is still valid when the transaction is 440 // applied. If an abort happens, it observes the new condition and 441 // retries. In theory, a worst case will try at most all of the 442 // conditions once, because units cannot join a scope once its relation 443 // is dying. 444 // 445 // Keep in mind that in the first iteration of the loop it's possible 446 // to have a Dying relation with a smaller-than-real unit count, because 447 // Destroy changes the Life attribute in memory (units could join before 448 // the database is actually changed). 449 rulogger.Debugf("%v leaving scope: unit count %d, life %v", op.Description(), op.ru.relation.doc.UnitCount, op.ru.relation.doc.Life) 450 count, err := relationScopes.FindId(key).Count() 451 if op.FatalError(errors.Annotatef(err, "cannot examine scope for %s", op.Description())) { 452 return nil, err 453 } else if count == 0 { 454 return nil, jujutxn.ErrNoOperations 455 } 456 ops := []txn.Op{{ 457 C: relationScopesC, 458 Id: key, 459 Assert: txn.DocExists, 460 Remove: true, 461 }} 462 if op.ru.relation.doc.Life == Alive { 463 ops = append(ops, txn.Op{ 464 C: relationsC, 465 Id: op.ru.relation.doc.DocID, 466 Assert: bson.D{{"life", Alive}}, 467 Update: bson.D{{"$inc", bson.D{{"unitcount", -1}}}}, 468 }) 469 } else if op.ru.relation.doc.UnitCount > 1 { 470 ops = append(ops, txn.Op{ 471 C: relationsC, 472 Id: op.ru.relation.doc.DocID, 473 Assert: bson.D{{"unitcount", bson.D{{"$gt", 1}}}}, 474 Update: bson.D{{"$inc", bson.D{{"unitcount", -1}}}}, 475 }) 476 } else { 477 // When 'force' is set, this call will return needed operations 478 // and accumulate all operational errors encountered in the operation. 479 // If the 'force' is not set, any error will be fatal and no operations will be returned. 480 relOps, err := op.ru.relation.removeOps("", op.ru.unitName, &op.ForcedOperation) 481 if op.FatalError(err) { 482 return nil, err 483 } 484 ops = append(ops, relOps...) 485 } 486 if rulogger.IsTraceEnabled() { 487 rulogger.Tracef("leave scope ops for %s: %s", op.Description(), pretty.Sprint(ops)) 488 } 489 return ops, nil 490 } 491 492 // Valid returns whether this RelationUnit is one that can actually 493 // exist in the relation. For container-scoped relations, RUs can be 494 // created for subordinate units whose principal unit isn't a member 495 // of the relation. There are too many places that rely on being able 496 // to construct a nonsensical RU to query InScope or Joined, so we 497 // allow them to be constructed but they will always return false for 498 // Valid. 499 // TODO(babbageclunk): unpick the reliance on creating invalid RUs. 500 func (ru *RelationUnit) Valid() (bool, error) { 501 if ru.endpoint.Scope != charm.ScopeContainer || ru.isPrincipal { 502 return true, nil 503 } 504 // A subordinate container-scoped relation unit is valid if: 505 // the other end of the relation is also a subordinate charm 506 // or its principal unit is also a member of the relation. 507 appName, err := names.UnitApplication(ru.unitName) 508 if err != nil { 509 return false, errors.Trace(err) 510 } 511 var otherAppName string 512 for _, ep := range ru.relation.Endpoints() { 513 if ep.ApplicationName != appName { 514 otherAppName = ep.ApplicationName 515 } 516 } 517 if otherAppName == "" { 518 return false, errors.Errorf("couldn't find other endpoint") 519 } 520 otherApp, err := ru.st.Application(otherAppName) 521 if err != nil { 522 return false, errors.Trace(err) 523 } 524 if !otherApp.IsPrincipal() { 525 return true, nil 526 } 527 528 unit, err := ru.st.Unit(ru.unitName) 529 if err != nil { 530 return false, errors.Trace(err) 531 } 532 // No need to check the flag here - we know we're subordinate. 533 pName, _ := unit.PrincipalName() 534 principalAppName, err := names.UnitApplication(pName) 535 if err != nil { 536 return false, errors.Trace(err) 537 } 538 // If the other application is a principal, only allow it if it's in the relation. 539 _, err = ru.relation.Endpoint(principalAppName) 540 if errors.IsNotFound(err) { 541 return false, nil 542 } else if err != nil { 543 return false, errors.Trace(err) 544 } 545 return true, nil 546 } 547 548 // InScope returns whether the relation unit has entered scope and not left it. 549 func (ru *RelationUnit) InScope() (bool, error) { 550 return ru.inScope(nil) 551 } 552 553 // Joined returns whether the relation unit has entered scope and neither left 554 // it nor prepared to leave it. 555 func (ru *RelationUnit) Joined() (bool, error) { 556 return ru.inScope(bson.D{{"departing", bson.D{{"$ne", true}}}}) 557 } 558 559 // inScope returns whether a scope document exists satisfying the supplied 560 // selector. 561 func (ru *RelationUnit) inScope(sel bson.D) (bool, error) { 562 relationScopes, closer := ru.st.db().GetCollection(relationScopesC) 563 defer closer() 564 565 sel = append(sel, bson.D{{"_id", ru.key()}}...) 566 count, err := relationScopes.Find(sel).Count() 567 if err != nil { 568 return false, err 569 } 570 return count > 0, nil 571 } 572 573 // WatchScope returns a watcher which notifies of counterpart units 574 // entering and leaving the unit's scope. 575 func (ru *RelationUnit) WatchScope() *RelationScopeWatcher { 576 role := counterpartRole(ru.endpoint.Role) 577 return watchRelationScope(ru.st, ru.scope, role, ru.unitName) 578 } 579 580 func watchRelationScope( 581 st *State, scope string, role charm.RelationRole, ignore string, 582 ) *RelationScopeWatcher { 583 scope = scope + "#" + string(role) 584 return newRelationScopeWatcher(st, scope, ignore) 585 } 586 587 // Settings returns a Settings which allows access to the unit's settings 588 // within the relation. 589 func (ru *RelationUnit) Settings() (*Settings, error) { 590 s, err := readSettings(ru.st.db(), settingsC, ru.key()) 591 if err != nil { 592 return nil, errors.Annotatef(err, "unit %q", ru.unitName) 593 } 594 return s, nil 595 } 596 597 // ReadSettings returns a map holding the settings of the unit with the 598 // supplied name within this relation. An error will be returned if the 599 // relation no longer exists, or if the unit's application is not part of the 600 // relation, or the settings are invalid; but mere non-existence of the 601 // unit is not grounds for an error, because the unit settings are 602 // guaranteed to persist for the lifetime of the relation, regardless 603 // of the lifetime of the unit. 604 func (ru *RelationUnit) ReadSettings(uname string) (m map[string]interface{}, err error) { 605 defer errors.DeferredAnnotatef(&err, "cannot read settings for unit %q in relation %q", uname, ru.relation) 606 if !names.IsValidUnit(uname) { 607 return nil, fmt.Errorf("%q is not a valid unit name", uname) 608 } 609 key, err := ru.unitKey(uname) 610 if err != nil { 611 return nil, err 612 } 613 node, err := readSettings(ru.st.db(), settingsC, key) 614 if err != nil { 615 return nil, errors.Annotatef(err, "unit %q", uname) 616 } 617 return node.Map(), nil 618 } 619 620 // unitKey returns a string, based on the relation and the supplied unit name, 621 // which is used as a key for that unit within this relation in the settings, 622 // presence, and relationScopes collections. 623 func (ru *RelationUnit) unitKey(uname string) (string, error) { 624 uparts := strings.Split(uname, "/") 625 sname := uparts[0] 626 ep, err := ru.relation.Endpoint(sname) 627 if err != nil { 628 return "", err 629 } 630 return ru._key(string(ep.Role), uname), nil 631 } 632 633 // key returns a string, based on the relation and the current unit name, 634 // which is used as a key for that unit within this relation in the settings, 635 // presence, and relationScopes collections. 636 func (ru *RelationUnit) key() string { 637 return ru._key(string(ru.endpoint.Role), ru.unitName) 638 } 639 640 func (ru *RelationUnit) _key(role, unitname string) string { 641 parts := []string{ru.scope, role, unitname} 642 return strings.Join(parts, "#") 643 } 644 645 // relationScopeDoc represents a unit which is in a relation scope. 646 // The relation, container, role, and unit are all encoded in the key. 647 type relationScopeDoc struct { 648 DocID string `bson:"_id"` 649 Key string `bson:"key"` 650 ModelUUID string `bson:"model-uuid"` 651 Departing bool `bson:"departing"` 652 } 653 654 func (d *relationScopeDoc) unitName() string { 655 return unitNameFromScopeKey(d.Key) 656 } 657 658 func unitNameFromScopeKey(key string) string { 659 parts := strings.Split(key, "#") 660 return parts[len(parts)-1] 661 } 662 663 // unpackScopeKey returns the scope, role and unitname from the 664 // relation scope key. 665 func unpackScopeKey(key string) (string, string, string, error) { 666 if _, localID, ok := splitDocID(key); ok { 667 key = localID 668 } 669 parts := strings.Split(key, "#") 670 if len(parts) < 4 { 671 return "", "", "", errors.Errorf("%q has too few parts to be a relation scope key", key) 672 } 673 unitName := parts[len(parts)-1] 674 role := parts[len(parts)-2] 675 scope := strings.Join(parts[:len(parts)-2], "#") 676 return scope, role, unitName, nil 677 }