github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/modelgeneration.go (about) 1 // Copyright 2018 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 "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 21 "github.com/juju/juju/core/settings" 22 "github.com/juju/juju/mongo/utils" 23 stateerrors "github.com/juju/juju/state/errors" 24 ) 25 26 // itemChange is the state representation of a core settings ItemChange. 27 type itemChange struct { 28 Type int `bson:"type"` 29 Key string `bson:"key"` 30 OldValue interface{} `bson:"old,omitempty"` 31 NewValue interface{} `bson:"new,omitempty"` 32 } 33 34 // coreChange returns the core package representation of this change. 35 // Stored keys are unescaped. 36 func (c *itemChange) coreChange() settings.ItemChange { 37 return settings.ItemChange{ 38 Type: c.Type, 39 Key: utils.UnescapeKey(c.Key), 40 OldValue: c.OldValue, 41 NewValue: c.NewValue, 42 } 43 } 44 45 // generationDoc represents the state of a model generation in MongoDB. 46 type generationDoc struct { 47 DocId string `bson:"_id"` 48 TxnRevno int64 `bson:"txn-revno"` 49 50 // Name is the name given to this branch at creation. 51 // There should never be more than one branch, not applied or aborted, 52 // with the same name. Branch names can otherwise be re-used. 53 Name string `bson:"name"` 54 55 // GenerationId is a monotonically incrementing sequence, 56 // set when a branch is committed to the model. 57 // Branches that are not applied, or that have been aborted 58 // will not have a generation ID set. 59 GenerationId int `bson:"generation-id"` 60 61 // ModelUUID indicates the model to which this generation applies. 62 ModelUUID string `bson:"model-uuid"` 63 64 // AssignedUnits is a map of unit names that are in the generation, 65 // keyed by application name. 66 // An application ID can be present without any unit IDs, 67 // which indicates that it has configuration changes applied in the 68 // generation, but no units currently set to be in it. 69 AssignedUnits map[string][]string `bson:"assigned-units"` 70 71 // Config is all changes made to charm configuration under this branch. 72 Config map[string][]itemChange `bson:"charm-config"` 73 74 // TODO (manadart 2019-04-02): CharmURLs, Resources. 75 76 // Created is a Unix timestamp indicating when this generation was created. 77 Created int64 `bson:"created"` 78 79 // CreatedBy is the user who created this generation. 80 CreatedBy string `bson:"created-by"` 81 82 // Completed, if set, indicates when this generation was completed and 83 // effectively became the current model generation. 84 Completed int64 `bson:"completed"` 85 86 // CompletedBy is the user who committed this generation to the model. 87 CompletedBy string `bson:"completed-by"` 88 } 89 90 // Generation represents the state of a model generation. 91 type Generation struct { 92 st *State 93 doc generationDoc 94 } 95 96 func (g *Generation) BranchName() string { 97 return g.doc.Name 98 } 99 100 // GenerationId indicates the relative order that this branch was committed 101 // and had its changes applied to the whole model. 102 func (g *Generation) GenerationId() int { 103 return g.doc.GenerationId 104 } 105 106 // ModelUUID returns the ID of the model to which this generation applies. 107 func (g *Generation) ModelUUID() string { 108 return g.doc.ModelUUID 109 } 110 111 // AssignedUnits returns the unit names, keyed by application name 112 // that have been assigned to this generation. 113 func (g *Generation) AssignedUnits() map[string][]string { 114 return g.doc.AssignedUnits 115 } 116 117 // Config returns all changed charm configuration for the generation. 118 // The persisted objects are converted to core changes. 119 func (g *Generation) Config() map[string]settings.ItemChanges { 120 changes := make(map[string]settings.ItemChanges, len(g.doc.Config)) 121 for appName, appCfg := range g.doc.Config { 122 appChanges := make(settings.ItemChanges, len(appCfg)) 123 for i, ch := range appCfg { 124 appChanges[i] = ch.coreChange() 125 } 126 sort.Sort(appChanges) 127 changes[appName] = appChanges 128 } 129 return changes 130 } 131 132 // Created returns the Unix timestamp at generation creation. 133 func (g *Generation) Created() int64 { 134 return g.doc.Created 135 } 136 137 // CreatedBy returns the user who created the generation. 138 func (g *Generation) CreatedBy() string { 139 return g.doc.CreatedBy 140 } 141 142 // IsCompleted returns true if the generation has been completed; 143 // i.e it has a completion time-stamp. 144 func (g *Generation) IsCompleted() bool { 145 return g.doc.Completed > 0 146 } 147 148 // Completed returns the Unix timestamp at generation completion. 149 func (g *Generation) Completed() int64 { 150 return g.doc.Completed 151 } 152 153 // CompletedBy returns the user who committed the generation. 154 func (g *Generation) CompletedBy() string { 155 return g.doc.CompletedBy 156 } 157 158 // AssignApplication indicates that the application with the input name has had 159 // changes in this generation. 160 func (g *Generation) AssignApplication(appName string) error { 161 buildTxn := func(attempt int) ([]txn.Op, error) { 162 if attempt > 0 { 163 if err := g.Refresh(); err != nil { 164 return nil, errors.Trace(err) 165 } 166 } 167 if _, ok := g.doc.AssignedUnits[appName]; ok { 168 return nil, jujutxn.ErrNoOperations 169 } 170 if err := g.CheckNotComplete(); err != nil { 171 return nil, err 172 } 173 return assignGenerationAppTxnOps(g.doc.DocId, appName), nil 174 } 175 176 return errors.Trace(g.st.db().Run(buildTxn)) 177 } 178 179 func assignGenerationAppTxnOps(id, appName string) []txn.Op { 180 assignedField := "assigned-units" 181 appField := fmt.Sprintf("%s.%s", assignedField, appName) 182 183 return []txn.Op{ 184 { 185 C: generationsC, 186 Id: id, 187 Assert: bson.D{{"$and", []bson.D{ 188 {{"completed", 0}}, 189 {{assignedField, bson.D{{"$exists", true}}}}, 190 {{appField, bson.D{{"$exists", false}}}}, 191 }}}, 192 Update: bson.D{ 193 {"$set", bson.D{{appField, []string{}}}}, 194 }, 195 }, 196 } 197 } 198 199 // AssignAllUnits ensures that all units of the input application are 200 // designated as tracking the branch, by adding the unit names 201 // to the generation. 202 func (g *Generation) AssignAllUnits(appName string) error { 203 return g.AssignUnits(appName, 0) 204 } 205 206 func (g *Generation) AssignUnits(appName string, numUnits int) error { 207 buildTxn := func(attempt int) ([]txn.Op, error) { 208 if attempt > 0 { 209 if err := g.Refresh(); err != nil { 210 return nil, errors.Trace(err) 211 } 212 } 213 if err := g.CheckNotComplete(); err != nil { 214 return nil, errors.Trace(err) 215 } 216 unitNames, err := appUnitNames(g.st, appName) 217 if err != nil { 218 return nil, errors.Trace(err) 219 } 220 app, err := g.st.Application(appName) 221 if err != nil { 222 return nil, errors.Trace(err) 223 } 224 ops := []txn.Op{ 225 { 226 C: applicationsC, 227 Id: app.doc.DocID, 228 Assert: bson.D{ 229 {"life", Alive}, 230 {"unitcount", app.doc.UnitCount}, 231 }, 232 }, 233 } 234 // Ensure we sort the unitNames so that when we ask for the numUnits 235 // to track, they're going to be predictable results. 236 sort.Strings(unitNames) 237 238 var assigned int 239 assignedUnits := set.NewStrings(g.doc.AssignedUnits[appName]...) 240 for _, name := range unitNames { 241 if !assignedUnits.Contains(name) { 242 if numUnits > 0 && numUnits == assigned { 243 break 244 } 245 unit, err := g.st.Unit(name) 246 if err != nil { 247 return nil, errors.Trace(err) 248 } 249 ops = append(ops, assignGenerationUnitTxnOps(g.doc.DocId, appName, unit)...) 250 assigned++ 251 } 252 } 253 // If there are no units to add to the generation, quit here. 254 if assigned == 0 { 255 return nil, jujutxn.ErrNoOperations 256 } 257 return ops, nil 258 } 259 return errors.Trace(g.st.db().Run(buildTxn)) 260 } 261 262 // AssignUnit indicates that the unit with the input name is tracking this 263 // branch, by adding the name to the generation. 264 func (g *Generation) AssignUnit(unitName string) error { 265 appName, err := names.UnitApplication(unitName) 266 if err != nil { 267 return errors.Trace(err) 268 } 269 270 buildTxn := func(attempt int) ([]txn.Op, error) { 271 if attempt > 0 { 272 if err := g.Refresh(); err != nil { 273 return nil, errors.Trace(err) 274 } 275 } 276 if err := g.CheckNotComplete(); err != nil { 277 return nil, errors.Trace(err) 278 } 279 if set.NewStrings(g.doc.AssignedUnits[appName]...).Contains(unitName) { 280 return nil, jujutxn.ErrNoOperations 281 } 282 unit, err := g.st.Unit(unitName) 283 if err != nil { 284 return nil, errors.Trace(err) 285 } 286 return assignGenerationUnitTxnOps(g.doc.DocId, appName, unit), nil 287 } 288 289 return errors.Trace(g.st.db().Run(buildTxn)) 290 } 291 292 func assignGenerationUnitTxnOps(id, appName string, unit *Unit) []txn.Op { 293 assignedField := "assigned-units" 294 appField := fmt.Sprintf("%s.%s", assignedField, appName) 295 296 return []txn.Op{ 297 { 298 C: unitsC, 299 Id: unit.doc.DocID, 300 Assert: bson.D{{"life", Alive}}, 301 }, 302 { 303 C: generationsC, 304 Id: id, 305 Assert: bson.D{{"$and", []bson.D{ 306 {{"completed", 0}}, 307 {{assignedField, bson.D{{"$exists", true}}}}, 308 {{appField, bson.D{{"$not", bson.D{{"$elemMatch", bson.D{{"$eq", unit.Name()}}}}}}}}, 309 }}}, 310 Update: bson.D{ 311 {"$push", bson.D{{appField, unit.Name()}}}, 312 }, 313 }, 314 } 315 } 316 317 // UpdateCharmConfig applies the input changes to the input application's 318 // charm configuration under this branch. 319 // the incoming charm settings are assumed to have been validated. 320 func (g *Generation) UpdateCharmConfig(appName string, master *Settings, validChanges charm.Settings) error { 321 buildTxn := func(attempt int) ([]txn.Op, error) { 322 if attempt > 0 { 323 if err := g.Refresh(); err != nil { 324 return nil, errors.Trace(err) 325 } 326 } 327 if err := g.CheckNotComplete(); err != nil { 328 return nil, errors.Trace(err) 329 } 330 331 // Apply the current branch deltas to the master settings. 332 branchChanges := g.Config() 333 branchDelta, branchHasDelta := branchChanges[appName] 334 if branchHasDelta { 335 master.applyChanges(branchDelta) 336 } 337 338 // Now apply the incoming changes on top and generate a new delta. 339 for k, v := range validChanges { 340 if v == nil { 341 master.Delete(k) 342 } else { 343 master.Set(k, v) 344 } 345 } 346 newDelta := master.changes() 347 348 // Ensure that the delta represents a change from master settings 349 // as they were when each setting was first modified under the branch. 350 if branchHasDelta { 351 var err error 352 if newDelta, err = newDelta.ApplyDeltaSource(branchDelta); err != nil { 353 return nil, errors.Trace(err) 354 } 355 } 356 357 return []txn.Op{ 358 { 359 C: generationsC, 360 Id: g.doc.DocId, 361 Assert: bson.D{{"$and", []bson.D{ 362 {{"completed", 0}}, 363 {{"txn-revno", g.doc.TxnRevno}}, 364 }}}, 365 Update: bson.D{ 366 {"$set", bson.D{{"charm-config." + appName, makeItemChanges(newDelta)}}}, 367 }, 368 }, 369 }, nil 370 } 371 372 return errors.Trace(g.st.db().Run(buildTxn)) 373 } 374 375 // Commit marks the generation as completed and assigns it the next value from 376 // the generation sequence. The new generation ID is returned. 377 func (g *Generation) Commit(userName string) (int, error) { 378 var newGenId int 379 380 buildTxn := func(attempt int) ([]txn.Op, error) { 381 if attempt > 0 { 382 if err := g.Refresh(); err != nil { 383 return nil, errors.Trace(err) 384 } 385 } 386 387 if g.IsCompleted() { 388 if g.GenerationId() == 0 { 389 return nil, errors.New("branch was already aborted") 390 } 391 return nil, jujutxn.ErrNoOperations 392 } 393 394 now, err := g.st.ControllerTimestamp() 395 if err != nil { 396 return nil, errors.Trace(err) 397 } 398 assigned, err := g.assignedWithAllUnits() 399 if err != nil { 400 return nil, errors.Trace(err) 401 } 402 ops, err := g.commitConfigTxnOps() 403 if err != nil { 404 return nil, errors.Trace(err) 405 } 406 407 // Get the new sequence as late as we can. 408 // If assigned is empty, indicating no changes under this branch, 409 // then the generation ID in not incremented. 410 // This effectively means the generation is aborted, not committed. 411 if len(assigned) > 0 { 412 id, err := sequenceWithMin(g.st, "generation", 1) 413 if err != nil { 414 return nil, errors.Trace(err) 415 } 416 newGenId = id 417 } 418 419 // As a proxy for checking that the generation has not changed, 420 // Assert that the txn rev-no has not changed since we materialised 421 // this generation object. 422 ops = append(ops, txn.Op{ 423 C: generationsC, 424 Id: g.doc.DocId, 425 Assert: bson.D{{"txn-revno", g.doc.TxnRevno}}, 426 Update: bson.D{ 427 {"$set", bson.D{ 428 {"assigned-units", assigned}, 429 {"completed", now.Unix()}, 430 {"completed-by", userName}, 431 {"generation-id", newGenId}, 432 }}, 433 }, 434 }) 435 return ops, nil 436 } 437 438 if err := g.st.db().Run(buildTxn); err != nil { 439 return 0, errors.Trace(err) 440 } 441 return newGenId, nil 442 } 443 444 // assignedWithAllUnits generates a new value for the branch's 445 // AssignedUnits field, to indicate that all units of changed applications 446 // are tracking the branch. 447 func (g *Generation) assignedWithAllUnits() (map[string][]string, error) { 448 assigned := g.AssignedUnits() 449 for app := range assigned { 450 units, err := appUnitNames(g.st, app) 451 if err != nil { 452 return nil, errors.Trace(err) 453 } 454 assigned[app] = units 455 } 456 return assigned, nil 457 } 458 459 // commitConfigTxnOps iterates over all the applications with configuration 460 // deltas, determines their effective new settings, then gathers the 461 // operations representing the changes so that they can all be applied in a 462 // single transaction. 463 func (g *Generation) commitConfigTxnOps() ([]txn.Op, error) { 464 var ops []txn.Op 465 for appName, delta := range g.Config() { 466 if len(delta) == 0 { 467 continue 468 } 469 app, err := g.st.Application(appName) 470 if err != nil { 471 return nil, errors.Trace(err) 472 } 473 474 // Apply the branch delta to the application's charm config settings. 475 cfg, err := readSettings(g.st.db(), settingsC, app.charmConfigKey()) 476 if err != nil { 477 return nil, errors.Trace(err) 478 } 479 cfg.applyChanges(delta) 480 481 _, updates := cfg.settingsUpdateOps() 482 // Assert that the settings document has not changed underneath us 483 // in addition to appending the field changes. 484 if len(updates) > 0 { 485 ops = append(ops, cfg.assertUnchangedOp()) 486 ops = append(ops, updates...) 487 } 488 } 489 return ops, nil 490 } 491 492 // Abort marks the generation as completed however no value is assigned from 493 // the generation sequence. 494 func (g *Generation) Abort(userName string) error { 495 buildTxn := func(attempt int) ([]txn.Op, error) { 496 if attempt > 0 { 497 if err := g.Refresh(); err != nil { 498 return nil, errors.Trace(err) 499 } 500 } 501 502 if g.IsCompleted() { 503 if g.GenerationId() > 0 { 504 return nil, errors.New("branch was already committed") 505 } 506 return nil, jujutxn.ErrNoOperations 507 } 508 509 // Must have no assigned units. 510 assigned := g.AssignedUnits() 511 for _, units := range assigned { 512 if len(units) > 0 { 513 return nil, errors.New("branch is in progress. Either reset values on tracking units and commit the branch or remove them to abort.") 514 } 515 } 516 517 // Must not have upgraded charm of tracked application. 518 // TODO (hml) 2019-06-26 519 // Implement cannot abort branch where tracked application has 520 // been upgraded. 521 522 now, err := g.st.ControllerTimestamp() 523 if err != nil { 524 return nil, errors.Trace(err) 525 } 526 // As a proxy for checking that the generation has not changed, 527 // Assert that the txn rev-no has not changed since we materialised 528 // this generation object. 529 ops := []txn.Op{{ 530 C: generationsC, 531 Id: g.doc.DocId, 532 Assert: bson.D{{"txn-revno", g.doc.TxnRevno}}, 533 Update: bson.D{ 534 {"$set", bson.D{ 535 {"completed", now.Unix()}, 536 {"completed-by", userName}, 537 }}, 538 }, 539 }} 540 return ops, nil 541 } 542 543 return errors.Trace(g.st.db().Run(buildTxn)) 544 } 545 546 // CheckNotComplete returns an error if this 547 // generation was committed or aborted. 548 func (g *Generation) CheckNotComplete() error { 549 if g.doc.Completed == 0 { 550 return nil 551 } 552 553 msg := "committed" 554 if g.doc.GenerationId == 0 { 555 msg = "aborted" 556 } 557 return errors.New("branch was already " + msg) 558 } 559 560 // Refresh refreshes the contents of the generation from the underlying state. 561 func (g *Generation) Refresh() error { 562 col, closer := g.st.db().GetCollection(generationsC) 563 defer closer() 564 565 var doc generationDoc 566 if err := col.FindId(g.doc.DocId).One(&doc); err != nil { 567 return errors.Trace(err) 568 } 569 g.doc = doc 570 return nil 571 } 572 573 // IsTracking returns true if the generation is tracking the provided unit. 574 func (g *Generation) IsTracking(unitName string) bool { 575 var tracked bool 576 for _, v := range g.doc.AssignedUnits { 577 if tracked = set.NewStrings(v...).Contains(unitName); tracked { 578 break 579 } 580 } 581 return tracked 582 } 583 584 func (g *Generation) unassignUnitOps(unitName, appName string) []txn.Op { 585 assignedField := "assigned-units" 586 appField := fmt.Sprintf("%s.%s", assignedField, appName) 587 588 // As a proxy for checking that the generation has not changed, 589 // Assert that the txn rev-no has not changed since we materialised 590 // this generation object. 591 return []txn.Op{{ 592 C: generationsC, 593 Id: g.doc.DocId, 594 Assert: bson.D{{"txn-revno", g.doc.TxnRevno}}, 595 Update: bson.D{ 596 {"$pull", bson.D{{appField, unitName}}}, 597 }, 598 }} 599 } 600 601 // HasChangesFor returns true when the generation has config changes for 602 // the provided application. 603 func (g *Generation) HasChangesFor(appName string) bool { 604 _, ok := g.doc.Config[appName] 605 return ok 606 } 607 608 // unassignAppOps returns operations to remove the tracking and config data 609 // for the application from the generation. 610 func (g *Generation) unassignAppOps(appName string) []txn.Op { 611 assigned := g.doc.AssignedUnits 612 delete(assigned, appName) 613 ops := []txn.Op{{ 614 C: generationsC, 615 Id: g.doc.DocId, 616 Assert: bson.D{{"txn-revno", g.doc.TxnRevno}}, 617 Update: bson.D{ 618 {"$set", bson.D{{"assigned-units", assigned}}}, 619 }, 620 }} 621 currentCfg := g.doc.Config 622 if _, ok := currentCfg[appName]; ok { 623 newCfg := map[string][]itemChange{} 624 for app, cfg := range currentCfg { 625 if app == appName { 626 continue 627 } 628 newCfg[app] = cfg 629 } 630 ops = append(ops, txn.Op{ 631 C: generationsC, 632 Id: g.doc.DocId, 633 Assert: bson.D{{"txn-revno", g.doc.TxnRevno}}, 634 Update: bson.D{ 635 {"$set", bson.D{{"charm-config", newCfg}}}, 636 }, 637 }) 638 } 639 return ops 640 } 641 642 // AddBranch creates a new branch in the current model. 643 func (m *Model) AddBranch(branchName, userName string) error { 644 return errors.Trace(m.st.AddBranch(branchName, userName)) 645 } 646 647 // AddBranch creates a new branch in the current model. 648 // A branch cannot be created with the same name as another "in-flight" branch. 649 // The input user indicates the operator who invoked the creation. 650 func (st *State) AddBranch(branchName, userName string) error { 651 id, err := sequence(st, "branch") 652 if err != nil { 653 return errors.Trace(err) 654 } 655 656 buildTxn := func(attempt int) ([]txn.Op, error) { 657 if _, err := st.Branch(branchName); err != nil { 658 if !errors.IsNotFound(err) { 659 return nil, errors.Annotatef(err, "checking for existing branch") 660 } 661 } else { 662 return nil, errors.Errorf("model already has branch %q", branchName) 663 } 664 665 now, err := st.ControllerTimestamp() 666 if err != nil { 667 return nil, errors.Trace(err) 668 } 669 return insertGenerationTxnOps(strconv.Itoa(id), branchName, userName, now), nil 670 } 671 err = st.db().Run(buildTxn) 672 if err != nil { 673 err = onAbort(err, stateerrors.ErrDead) 674 logger.Errorf("cannot add branch to the model: %v", err) 675 } 676 return err 677 } 678 679 func insertGenerationTxnOps(id, branchName, userName string, now *time.Time) []txn.Op { 680 doc := &generationDoc{ 681 Name: branchName, 682 AssignedUnits: map[string][]string{}, 683 Created: now.Unix(), 684 CreatedBy: userName, 685 } 686 687 return []txn.Op{ 688 { 689 C: generationsC, 690 Id: id, 691 Insert: doc, 692 }, 693 } 694 } 695 696 // Generations returns all committed branches. 697 func (m *Model) Generations() ([]*Generation, error) { 698 b, err := m.st.CommittedBranches() 699 return b, errors.Trace(err) 700 } 701 702 // Branches returns all "in-flight" branches for the model. 703 func (m *Model) Branches() ([]*Generation, error) { 704 b, err := m.st.Branches() 705 return b, errors.Trace(err) 706 } 707 708 // Branches returns all "in-flight" branches. 709 func (st *State) Branches() ([]*Generation, error) { 710 col, closer := st.db().GetCollection(generationsC) 711 defer closer() 712 713 var docs []generationDoc 714 if err := col.Find(bson.M{"completed": 0}).All(&docs); err != nil { 715 return nil, errors.Trace(err) 716 } 717 718 branches := make([]*Generation, len(docs)) 719 for i, d := range docs { 720 branches[i] = newGeneration(st, &d) 721 } 722 return branches, nil 723 } 724 725 // CommittedBranches returns all committed branches. 726 func (st *State) CommittedBranches() ([]*Generation, error) { 727 col, closer := st.db().GetCollection(generationsC) 728 defer closer() 729 730 var docs []generationDoc 731 query := bson.M{"generation-id": bson.M{"$gte": 1}} 732 if err := col.Find(query).All(&docs); err != nil { 733 return nil, errors.Trace(err) 734 } 735 736 branches := make([]*Generation, len(docs)) 737 for i, d := range docs { 738 branches[i] = newGeneration(st, &d) 739 } 740 return branches, nil 741 } 742 743 // Branch retrieves the generation with the the input branch name from the 744 // collection of not-yet-completed generations. 745 func (m *Model) Branch(name string) (*Generation, error) { 746 gen, err := m.st.Branch(name) 747 return gen, errors.Trace(err) 748 } 749 750 // Generation retrieves the generation with the the input generation_id from the 751 // collection of completed generations. 752 func (m *Model) Generation(id int) (*Generation, error) { 753 gen, err := m.st.CommittedBranch(id) 754 return gen, errors.Trace(err) 755 } 756 757 func (m *Model) applicationBranches(appName string) ([]*Generation, error) { 758 branches, err := m.Branches() 759 if err != nil { 760 return nil, errors.Trace(err) 761 } 762 foundBranches := make([]*Generation, 0) 763 for _, branch := range branches { 764 if branch.HasChangesFor(appName) { 765 foundBranches = append(foundBranches, branch) 766 continue 767 } 768 if _, ok := branch.doc.AssignedUnits[appName]; ok { 769 foundBranches = append(foundBranches, branch) 770 } 771 } 772 return foundBranches, nil 773 } 774 775 // Branch retrieves the generation with the the input branch name from the 776 // collection of not-yet-completed generations. 777 func (st *State) Branch(name string) (*Generation, error) { 778 doc, err := st.getBranchDoc(name) 779 if err != nil { 780 return nil, errors.Trace(err) 781 } 782 return newGeneration(st, doc), nil 783 } 784 785 // Generation retrieves the generation with the the input id from the 786 // collection of completed generations. 787 func (st *State) CommittedBranch(id int) (*Generation, error) { 788 doc, err := st.getCommittedBranchDoc(id) 789 if err != nil { 790 return nil, errors.Trace(err) 791 } 792 return newGeneration(st, doc), nil 793 } 794 795 func (st *State) getBranchDoc(name string) (*generationDoc, error) { 796 col, closer := st.db().GetCollection(generationsC) 797 defer closer() 798 799 doc := &generationDoc{} 800 err := col.Find(bson.M{ 801 "name": name, 802 "completed": 0, 803 }).One(doc) 804 805 switch err { 806 case nil: 807 return doc, nil 808 case mgo.ErrNotFound: 809 mod, _ := st.modelName() 810 return nil, errors.NotFoundf("branch %q in model %q", name, mod) 811 default: 812 mod, _ := st.modelName() 813 return nil, errors.Annotatef(err, "retrieving branch %q in model %q", name, mod) 814 } 815 } 816 817 func (st *State) getCommittedBranchDoc(id int) (*generationDoc, error) { 818 col, closer := st.db().GetCollection(generationsC) 819 defer closer() 820 821 doc := &generationDoc{} 822 err := col.Find(bson.M{ 823 "generation-id": id, 824 }).One(doc) 825 826 switch err { 827 case nil: 828 return doc, nil 829 case mgo.ErrNotFound: 830 mod, _ := st.modelName() 831 return nil, errors.NotFoundf("generation_id %d in model %q", id, mod) 832 default: 833 mod, _ := st.modelName() 834 return nil, errors.Annotatef(err, "retrieving generation_id %q in model %q", id, mod) 835 } 836 } 837 838 func (m *Model) unitBranch(unitName string) (*Generation, error) { 839 // NOTE (hml) 2019-07-02 840 // Currently a unit may only be tracked in a single generation. 841 // The branches spec indicates that may change in the future. If 842 // it does, this method and caller will need to be updated accordingly. 843 branches, err := m.Branches() 844 if err != nil { 845 return nil, errors.Trace(err) 846 } 847 for _, b := range branches { 848 if b.IsTracking(unitName) { 849 return b, nil 850 } 851 } 852 return nil, nil 853 } 854 855 func newGeneration(st *State, doc *generationDoc) *Generation { 856 return &Generation{ 857 st: st, 858 doc: *doc, 859 } 860 } 861 862 // makeItemChanges generates a persistable collection of changes from a core 863 // settings representation, with keys escaped for Mongo. 864 func makeItemChanges(coreChanges settings.ItemChanges) []itemChange { 865 changes := make([]itemChange, len(coreChanges)) 866 for i, c := range coreChanges { 867 changes[i] = itemChange{ 868 Type: c.Type, 869 Key: utils.EscapeKey(c.Key), 870 OldValue: c.OldValue, 871 NewValue: c.NewValue, 872 } 873 } 874 return changes 875 } 876 877 // branchesCleanupChange removes the generation doc. 878 type branchesCleanupChange struct{} 879 880 // Prepare is part of the Change interface. 881 func (change branchesCleanupChange) Prepare(db Database) ([]txn.Op, error) { 882 generations, closer := db.GetCollection(generationsC) 883 defer closer() 884 885 var docs []struct { 886 DocID string `bson:"_id"` 887 } 888 err := generations.Find(nil).Select(bson.D{{"_id", 1}}).All(&docs) 889 if err != nil { 890 return nil, errors.Trace(err) 891 } 892 if len(docs) == 0 { 893 return nil, ErrChangeComplete 894 } 895 896 ops := make([]txn.Op, len(docs)) 897 for i, doc := range docs { 898 ops[i] = txn.Op{ 899 C: generationsC, 900 Id: doc.DocID, 901 Remove: true, 902 } 903 } 904 return ops, nil 905 906 }