github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/modelmigration.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "strings" 10 "time" 11 12 "github.com/juju/errors" 13 "github.com/juju/mgo/v3" 14 "github.com/juju/mgo/v3/bson" 15 "github.com/juju/mgo/v3/txn" 16 "github.com/juju/names/v5" 17 "gopkg.in/macaroon.v2" 18 19 "github.com/juju/juju/core/migration" 20 "github.com/juju/juju/core/permission" 21 "github.com/juju/juju/core/status" 22 "github.com/juju/juju/mongo" 23 ) 24 25 // This file contains functionality for managing the state documents 26 // used by Juju to track model migrations. 27 28 // ModelMigration represents the state of an migration attempt for a 29 // model. 30 type ModelMigration interface { 31 // Id returns a unique identifier for the model migration. 32 Id() string 33 34 // ModelUUID returns the UUID for the model being migrated. 35 ModelUUID() string 36 37 // Attempt returns the migration attempt identifier. This 38 // increments for each migration attempt for the model. 39 Attempt() int 40 41 // StartTime returns the time when the migration was started. 42 StartTime() time.Time 43 44 // SuccessTime returns the time when the migration reached 45 // SUCCESS. 46 SuccessTime() time.Time 47 48 // EndTime returns the time when the migration reached DONE or 49 // REAPFAILED. 50 EndTime() time.Time 51 52 // Phase returns the migration's phase. 53 Phase() (migration.Phase, error) 54 55 // PhaseChangedTime returns the time when the migration's phase 56 // last changed. 57 PhaseChangedTime() time.Time 58 59 // StatusMessage returns human readable text about the current 60 // progress of the migration. 61 StatusMessage() string 62 63 // InitiatedBy returns username the initiated the migration. 64 InitiatedBy() string 65 66 // TargetInfo returns the details required to connect to the 67 // migration's target controller. 68 TargetInfo() (*migration.TargetInfo, error) 69 70 // SetPhase sets the phase of the migration. An error will be 71 // returned if the new phase does not follow the current phase or 72 // if the migration is no longer active. 73 SetPhase(nextPhase migration.Phase) error 74 75 // SetStatusMessage sets some human readable text about the 76 // current progress of the migration. 77 SetStatusMessage(text string) error 78 79 // SubmitMinionReport records a report from a migration minion 80 // worker about the success or failure to complete its actions for 81 // a given migration phase. 82 SubmitMinionReport(tag names.Tag, phase migration.Phase, success bool) error 83 84 // MinionReports returns details of the minions that have reported 85 // success or failure for the current migration phase, as well as 86 // those which are yet to report. 87 MinionReports() (*MinionReports, error) 88 89 // WatchMinionReports returns a notify watcher which triggers when 90 // a migration minion has reported back about the success or failure 91 // of its actions for the current migration phase. 92 WatchMinionReports() (NotifyWatcher, error) 93 94 // Refresh updates the contents of the ModelMigration from the 95 // underlying state. 96 Refresh() error 97 98 // ModelUserAccess returns the type of access that the given tag had to 99 // the model prior to it being migrated. 100 ModelUserAccess(names.Tag) permission.Access 101 } 102 103 // MinionReports indicates the sets of agents whose migration minion 104 // workers have completed the current migration phase, have failed to 105 // complete the current migration phase, or are yet to report 106 // regarding the current migration phase. 107 type MinionReports struct { 108 Succeeded []names.Tag 109 Failed []names.Tag 110 Unknown []names.Tag 111 } 112 113 // modelMigration is an implementation of ModelMigration. 114 type modelMigration struct { 115 st *State 116 doc modelMigDoc 117 statusDoc modelMigStatusDoc 118 } 119 120 // modelMigDoc holds parameters of a migration attempt for a 121 // model. These are written into migrationsC. 122 type modelMigDoc struct { 123 // Id holds migration document key. It has the format 124 // "uuid:sequence". 125 Id string `bson:"_id"` 126 127 // The UUID of the model being migrated. 128 ModelUUID string `bson:"model-uuid"` 129 130 // The attempt number of the model migration for this model. 131 Attempt int `bson:"attempt"` 132 133 // InitiatedBy holds the username of the user that triggered the 134 // migration. It should be in "user@domain" format. 135 InitiatedBy string `bson:"initiated-by"` 136 137 // TargetController holds the UUID of the target controller. 138 TargetController string `bson:"target-controller"` 139 140 // An optional alias for the controller the model got migrated to. 141 TargetControllerAlias string `bson:"target-controller-alias"` 142 143 // TargetAddrs holds the host:port values for the target API 144 // server. 145 TargetAddrs []string `bson:"target-addrs"` 146 147 // TargetCACert holds the certificate to validate the target API 148 // server's TLS certificate. 149 TargetCACert string `bson:"target-cacert"` 150 151 // TargetAuthTag holds a string representation of the tag to 152 // authenticate to the target controller with. 153 TargetAuthTag string `bson:"target-entity"` 154 155 // TargetPassword holds the password to use with TargetAuthTag 156 // when authenticating. 157 TargetPassword string `bson:"target-password,omitempty"` 158 159 // TargetMacaroons holds the macaroons to use with TargetAuthTag 160 // when authenticating. 161 TargetMacaroons string `bson:"target-macaroons,omitempty"` 162 163 // The list of users and their access-level to the model being migrated. 164 ModelUsers []modelMigUserDoc `bson:"model-users,omitempty"` 165 } 166 167 type modelMigUserDoc struct { 168 UserID string `bson:"user_id"` 169 Access permission.Access `bson:"access"` 170 } 171 172 // modelMigStatusDoc tracks the progress of a migration attempt for a 173 // model. These are written into migrationsStatusC. 174 // 175 // There is exactly one document in migrationsStatusC for each 176 // document in migrationsC. Separating them allows for watching 177 // for new model migrations without being woken up for each model 178 // migration status change. 179 type modelMigStatusDoc struct { 180 // These are the same as the ids as migrationsC. 181 // "uuid:sequence". 182 Id string `bson:"_id"` 183 184 // StartTime holds the time the migration started (stored as per 185 // UnixNano). 186 StartTime int64 `bson:"start-time"` 187 188 // StartTime holds the time the migration reached the SUCCESS 189 // phase (stored as per UnixNano). 190 SuccessTime int64 `bson:"success-time"` 191 192 // EndTime holds the time the migration reached a terminal (end) 193 // phase (stored as per UnixNano). 194 EndTime int64 `bson:"end-time"` 195 196 // Phase holds the current migration phase. This should be one of 197 // the string representations of the core/migrations.Phase 198 // constants. 199 Phase string `bson:"phase"` 200 201 // PhaseChangedTime holds the time that Phase last changed (stored 202 // as per UnixNano). 203 PhaseChangedTime int64 `bson:"phase-changed-time"` 204 205 // StatusMessage holds a human readable message about the 206 // migration's progress. 207 StatusMessage string `bson:"status-message"` 208 } 209 210 type modelMigMinionSyncDoc struct { 211 Id string `bson:"_id"` 212 MigrationId string `bson:"migration-id"` 213 Phase string `bson:"phase"` 214 EntityKey string `bson:"entity-key"` 215 Time int64 `bson:"time"` 216 Success bool `bson:"success"` 217 } 218 219 // Id implements ModelMigration. 220 func (mig *modelMigration) Id() string { 221 return mig.doc.Id 222 } 223 224 // ModelUUID implements ModelMigration. 225 func (mig *modelMigration) ModelUUID() string { 226 return mig.doc.ModelUUID 227 } 228 229 // Attempt implements ModelMigration. 230 func (mig *modelMigration) Attempt() int { 231 return mig.doc.Attempt 232 } 233 234 // StartTime implements ModelMigration. 235 func (mig *modelMigration) StartTime() time.Time { 236 return unixNanoToTime0(mig.statusDoc.StartTime) 237 } 238 239 // SuccessTime implements ModelMigration. 240 func (mig *modelMigration) SuccessTime() time.Time { 241 return unixNanoToTime0(mig.statusDoc.SuccessTime) 242 } 243 244 // EndTime implements ModelMigration. 245 func (mig *modelMigration) EndTime() time.Time { 246 return unixNanoToTime0(mig.statusDoc.EndTime) 247 } 248 249 // Phase implements ModelMigration. 250 func (mig *modelMigration) Phase() (migration.Phase, error) { 251 phase, ok := migration.ParsePhase(mig.statusDoc.Phase) 252 if !ok { 253 return phase, errors.Errorf("invalid phase in DB: %v", mig.statusDoc.Phase) 254 } 255 return phase, nil 256 } 257 258 // PhaseChangedTime implements ModelMigration. 259 func (mig *modelMigration) PhaseChangedTime() time.Time { 260 return unixNanoToTime0(mig.statusDoc.PhaseChangedTime) 261 } 262 263 // StatusMessage implements ModelMigration. 264 func (mig *modelMigration) StatusMessage() string { 265 return mig.statusDoc.StatusMessage 266 } 267 268 // InitiatedBy implements ModelMigration. 269 func (mig *modelMigration) InitiatedBy() string { 270 return mig.doc.InitiatedBy 271 } 272 273 // TargetInfo implements ModelMigration. 274 func (mig *modelMigration) TargetInfo() (*migration.TargetInfo, error) { 275 authTag, err := names.ParseUserTag(mig.doc.TargetAuthTag) 276 if err != nil { 277 return nil, errors.Trace(err) 278 } 279 macs, err := jsonToMacaroons(mig.doc.TargetMacaroons) 280 if err != nil { 281 return nil, errors.Trace(err) 282 } 283 return &migration.TargetInfo{ 284 ControllerTag: names.NewControllerTag(mig.doc.TargetController), 285 ControllerAlias: mig.doc.TargetControllerAlias, 286 Addrs: mig.doc.TargetAddrs, 287 CACert: mig.doc.TargetCACert, 288 AuthTag: authTag, 289 Password: mig.doc.TargetPassword, 290 Macaroons: macs, 291 }, nil 292 } 293 294 // SetPhase implements ModelMigration. 295 func (mig *modelMigration) SetPhase(nextPhase migration.Phase) error { 296 now := mig.st.clock().Now().UnixNano() 297 298 phase, err := mig.Phase() 299 if err != nil { 300 return errors.Trace(err) 301 } 302 303 if nextPhase == phase { 304 return nil // Already at that phase. Nothing to do. 305 } 306 if !phase.CanTransitionTo(nextPhase) { 307 return errors.Errorf("illegal phase change: %s -> %s", phase, nextPhase) 308 } 309 310 nextDoc := mig.statusDoc 311 nextDoc.Phase = nextPhase.String() 312 nextDoc.PhaseChangedTime = now 313 update := bson.M{ 314 "phase": nextDoc.Phase, 315 "phase-changed-time": now, 316 } 317 if nextPhase == migration.SUCCESS { 318 nextDoc.SuccessTime = now 319 update["success-time"] = now 320 } 321 322 ops, err := migStatusHistoryAndOps(mig.st, nextPhase, now, mig.StatusMessage()) 323 if err != nil { 324 return errors.Trace(err) 325 } 326 327 // If the migration aborted, make the model active again. 328 if nextPhase == migration.ABORTDONE { 329 ops = append(ops, txn.Op{ 330 C: modelsC, 331 Id: mig.doc.ModelUUID, 332 Assert: txn.DocExists, 333 Update: bson.M{ 334 "$set": bson.M{"migration-mode": MigrationModeNone}, 335 }, 336 }) 337 } 338 339 // Set end timestamps and mark migration as no longer active if a 340 // terminal phase is hit. 341 if nextPhase.IsTerminal() { 342 nextDoc.EndTime = now 343 update["end-time"] = now 344 ops = append(ops, txn.Op{ 345 C: migrationsActiveC, 346 Id: mig.doc.ModelUUID, 347 Assert: txn.DocExists, 348 Remove: true, 349 }) 350 } 351 352 ops = append(ops, txn.Op{ 353 C: migrationsStatusC, 354 Id: mig.statusDoc.Id, 355 Update: bson.M{"$set": update}, 356 // Ensure phase hasn't changed underneath us 357 Assert: bson.M{"phase": mig.statusDoc.Phase}, 358 }) 359 360 if err := mig.st.db().RunTransaction(ops); err == txn.ErrAborted { 361 return errors.New("phase already changed") 362 } else if err != nil { 363 return errors.Annotate(err, "failed to update phase") 364 } 365 366 mig.statusDoc = nextDoc 367 return nil 368 } 369 370 // migStatusHistoryAndOps sets the model's status history and returns ops for 371 // setting model status according to the phase and message. 372 func migStatusHistoryAndOps(st *State, phase migration.Phase, now int64, msg string) ([]txn.Op, error) { 373 switch phase { 374 case migration.REAP, migration.DONE: 375 // if we're reaping/have reaped the model, setting status on it is both 376 // pointless and potentially problematic. 377 return nil, nil 378 } 379 model, err := st.Model() 380 if err != nil { 381 return nil, errors.Trace(err) 382 } 383 globalKey := model.globalKey() 384 modelStatus := status.Busy 385 if phase.IsTerminal() { 386 modelStatus = status.Available 387 } 388 if msg != "" { 389 msg = "migrating: " + msg 390 } 391 doc := statusDoc{ 392 Status: modelStatus, 393 StatusInfo: msg, 394 Updated: now, 395 } 396 397 ops, err := statusSetOps(st.db(), doc, globalKey) 398 if err != nil { 399 return nil, errors.Trace(err) 400 } 401 _, _ = probablyUpdateStatusHistory(st.db(), globalKey, doc) 402 return ops, nil 403 } 404 405 // SetStatusMessage implements ModelMigration. 406 func (mig *modelMigration) SetStatusMessage(text string) error { 407 phase, err := mig.Phase() 408 if err != nil { 409 return errors.Trace(err) 410 } 411 412 ops, err := migStatusHistoryAndOps(mig.st, phase, mig.st.clock().Now().UnixNano(), text) 413 if err != nil { 414 return errors.Trace(err) 415 } 416 417 ops = append(ops, txn.Op{ 418 C: migrationsStatusC, 419 Id: mig.statusDoc.Id, 420 Update: bson.M{"$set": bson.M{"status-message": text}}, 421 Assert: txn.DocExists, 422 }) 423 if err := mig.st.db().RunTransaction(ops); err != nil { 424 return errors.Annotate(err, "failed to set migration status") 425 } 426 mig.statusDoc.StatusMessage = text 427 return nil 428 } 429 430 // SubmitMinionReport implements ModelMigration. 431 func (mig *modelMigration) SubmitMinionReport(tag names.Tag, phase migration.Phase, success bool) error { 432 globalKey, err := agentTagToGlobalKey(tag) 433 if err != nil { 434 return errors.Trace(err) 435 } 436 docID := mig.minionReportId(phase, globalKey) 437 doc := modelMigMinionSyncDoc{ 438 Id: docID, 439 MigrationId: mig.Id(), 440 Phase: phase.String(), 441 EntityKey: globalKey, 442 Time: mig.st.clock().Now().UnixNano(), 443 Success: success, 444 } 445 ops := []txn.Op{{ 446 C: migrationsMinionSyncC, 447 Id: docID, 448 Insert: doc, 449 Assert: txn.DocMissing, 450 }} 451 err = mig.st.db().RunTransaction(ops) 452 if errors.Cause(err) == txn.ErrAborted { 453 coll, closer := mig.st.db().GetCollection(migrationsMinionSyncC) 454 defer closer() 455 var existingDoc modelMigMinionSyncDoc 456 err := coll.FindId(docID).Select(bson.M{"success": 1}).One(&existingDoc) 457 if err != nil { 458 return errors.Annotate(err, "checking existing report") 459 } 460 if existingDoc.Success != success { 461 return errors.Errorf("conflicting reports received for %s/%s/%s", 462 mig.Id(), phase.String(), tag) 463 } 464 return nil 465 } else if err != nil { 466 return errors.Trace(err) 467 } 468 return nil 469 } 470 471 // MinionReports implements ModelMigration. 472 func (mig *modelMigration) MinionReports() (*MinionReports, error) { 473 all, err := mig.getAllAgents() 474 if err != nil { 475 return nil, errors.Trace(err) 476 } 477 478 phase, err := mig.Phase() 479 if err != nil { 480 return nil, errors.Annotate(err, "retrieving phase") 481 } 482 483 coll, closer := mig.st.db().GetCollection(migrationsMinionSyncC) 484 defer closer() 485 query := coll.Find(bson.M{"_id": bson.M{ 486 "$regex": "^" + mig.minionReportId(phase, ".+"), 487 }}) 488 query = query.Select(bson.M{ 489 "entity-key": 1, 490 "success": 1, 491 }) 492 var docs []bson.M 493 if err := query.All(&docs); err != nil { 494 return nil, errors.Annotate(err, "retrieving minion reports") 495 } 496 497 succeeded := names.NewSet() 498 failed := names.NewSet() 499 for _, doc := range docs { 500 entityKey, ok := doc["entity-key"].(string) 501 if !ok { 502 return nil, errors.Errorf("unexpected entity-key %v", doc["entity-key"]) 503 } 504 tag, err := globalKeyToAgentTag(entityKey) 505 if err != nil { 506 return nil, errors.Trace(err) 507 } 508 success, ok := doc["success"].(bool) 509 if !ok { 510 return nil, errors.Errorf("unexpected success value: %v", doc["success"]) 511 } 512 if success { 513 succeeded.Add(tag) 514 } else { 515 failed.Add(tag) 516 } 517 } 518 519 unknown := all.Difference(succeeded).Difference(failed) 520 521 return &MinionReports{ 522 Succeeded: succeeded.Values(), 523 Failed: failed.Values(), 524 Unknown: unknown.Values(), 525 }, nil 526 } 527 528 // WatchMinionReports implements ModelMigration. 529 func (mig *modelMigration) WatchMinionReports() (NotifyWatcher, error) { 530 phase, err := mig.Phase() 531 if err != nil { 532 return nil, errors.Annotate(err, "retrieving phase") 533 } 534 prefix := mig.minionReportId(phase, "") 535 filter := func(rawId interface{}) bool { 536 id, ok := rawId.(string) 537 if !ok { 538 return false 539 } 540 return strings.HasPrefix(id, prefix) 541 } 542 return newNotifyCollWatcher(mig.st, migrationsMinionSyncC, filter), nil 543 } 544 545 func (mig *modelMigration) minionReportId(phase migration.Phase, globalKey string) string { 546 return fmt.Sprintf("%s:%s:%s", mig.Id(), phase.String(), globalKey) 547 } 548 549 func (mig *modelMigration) getAllAgents() (names.Set, error) { 550 agentTags := names.NewSet() 551 machineTags, err := mig.loadAgentTags(machinesC, "machineid", 552 func(id string) names.Tag { return names.NewMachineTag(id) }, 553 ) 554 if err != nil { 555 return nil, errors.Annotate(err, "loading machine tags") 556 } 557 agentTags = agentTags.Union(machineTags) 558 559 m, err := mig.st.Model() 560 if err != nil { 561 return nil, errors.Trace(err) 562 } 563 if m.Type() != ModelTypeCAAS { 564 unitTags, err := mig.loadAgentTags(unitsC, "name", 565 func(name string) names.Tag { return names.NewUnitTag(name) }, 566 ) 567 if err != nil { 568 return nil, errors.Annotate(err, "loading unit names") 569 } 570 571 return agentTags.Union(unitTags), nil 572 } 573 574 applicationTags, err := mig.loadAgentTags(applicationsC, "name", 575 func(name string) names.Tag { return names.NewApplicationTag(name) }, 576 ) 577 if err != nil { 578 return nil, errors.Annotate(err, "loading application names") 579 } 580 for _, applicationTag := range applicationTags.Values() { 581 app, err := mig.st.Application(applicationTag.Id()) 582 if err != nil { 583 return nil, errors.Trace(err) 584 } 585 isSidecar, err := app.IsSidecar() 586 if err != nil { 587 return nil, errors.Trace(err) 588 } 589 if !isSidecar { 590 agentTags.Add(applicationTag) 591 continue 592 } 593 unitNames, err := app.UnitNames() 594 if err != nil { 595 return nil, errors.Trace(err) 596 } 597 for _, unitName := range unitNames { 598 agentTags.Add(names.NewUnitTag(unitName)) 599 } 600 } 601 return agentTags, nil 602 } 603 604 func (mig *modelMigration) loadAgentTags(collName, fieldName string, convert func(string) names.Tag) ( 605 names.Set, error, 606 ) { 607 // During migrations we know that nothing there are no machines or 608 // units being provisioned or destroyed so a simple query of the 609 // collections will do. 610 coll, closer := mig.st.db().GetCollection(collName) 611 defer closer() 612 var docs []bson.M 613 err := coll.Find(nil).Select(bson.M{fieldName: 1}).All(&docs) 614 if err != nil { 615 return nil, errors.Trace(err) 616 } 617 618 out := names.NewSet() 619 for _, doc := range docs { 620 v, ok := doc[fieldName].(string) 621 if !ok { 622 return nil, errors.Errorf("invalid %s value: %v", fieldName, doc[fieldName]) 623 } 624 out.Add(convert(v)) 625 } 626 return out, nil 627 } 628 629 // Refresh implements ModelMigration. 630 func (mig *modelMigration) Refresh() error { 631 // Only the status document is updated. The modelMigDoc is static 632 // after creation. 633 statusColl, closer := mig.st.db().GetCollection(migrationsStatusC) 634 defer closer() 635 var statusDoc modelMigStatusDoc 636 err := statusColl.FindId(mig.doc.Id).One(&statusDoc) 637 if err == mgo.ErrNotFound { 638 return errors.NotFoundf("migration status") 639 } else if err != nil { 640 return errors.Annotate(err, "migration status lookup failed") 641 } 642 643 mig.statusDoc = statusDoc 644 return nil 645 } 646 647 // ModelUserAccess implements ModelMigration. 648 func (mig *modelMigration) ModelUserAccess(tag names.Tag) permission.Access { 649 id := tag.Id() 650 for _, user := range mig.doc.ModelUsers { 651 if user.UserID == id { 652 return user.Access 653 } 654 } 655 656 return permission.NoAccess 657 } 658 659 // MigrationSpec holds the information required to create a 660 // ModelMigration instance. 661 type MigrationSpec struct { 662 InitiatedBy names.UserTag 663 TargetInfo migration.TargetInfo 664 } 665 666 // Validate returns an error if the MigrationSpec contains bad 667 // data. Nil is returned otherwise. 668 func (spec *MigrationSpec) Validate() error { 669 if !names.IsValidUser(spec.InitiatedBy.Id()) { 670 return errors.NotValidf("InitiatedBy") 671 } 672 return spec.TargetInfo.Validate() 673 } 674 675 // CreateMigration initialises state that tracks a model migration. It 676 // will return an error if there is already a model migration in 677 // progress. 678 func (st *State) CreateMigration(spec MigrationSpec) (ModelMigration, error) { 679 if st.IsController() { 680 return nil, errors.New("controllers can't be migrated") 681 } 682 if err := spec.Validate(); err != nil { 683 return nil, errors.Trace(err) 684 } 685 if err := checkTargetController(st, spec.TargetInfo.ControllerTag); err != nil { 686 return nil, errors.Trace(err) 687 } 688 689 now := st.clock().Now().UnixNano() 690 modelUUID := st.ModelUUID() 691 var doc modelMigDoc 692 var statusDoc modelMigStatusDoc 693 694 msg := "starting" 695 ops, err := migStatusHistoryAndOps(st, migration.QUIESCE, now, msg) 696 if err != nil { 697 return nil, errors.Trace(err) 698 } 699 700 buildTxn := func(int) ([]txn.Op, error) { 701 model, err := st.Model() 702 if err != nil { 703 return nil, errors.Annotate(err, "failed to load model") 704 } 705 if model.Life() != Alive { 706 return nil, errors.New("model is not alive") 707 } 708 709 if isActive, err := st.IsMigrationActive(); err != nil { 710 return nil, errors.Trace(err) 711 } else if isActive { 712 return nil, errors.New("already in progress") 713 } 714 715 macsJSON, err := macaroonsToJSON(spec.TargetInfo.Macaroons) 716 if err != nil { 717 return nil, errors.Trace(err) 718 } 719 720 attempt, err := sequence(st, "modelmigration") 721 if err != nil { 722 return nil, errors.Trace(err) 723 } 724 725 userDocs, err := modelUserDocs(model) 726 if err != nil { 727 return nil, errors.Trace(err) 728 } 729 730 id := fmt.Sprintf("%s:%d", modelUUID, attempt) 731 doc = modelMigDoc{ 732 Id: id, 733 ModelUUID: modelUUID, 734 Attempt: attempt, 735 InitiatedBy: spec.InitiatedBy.Id(), 736 TargetController: spec.TargetInfo.ControllerTag.Id(), 737 TargetControllerAlias: spec.TargetInfo.ControllerAlias, 738 TargetAddrs: spec.TargetInfo.Addrs, 739 TargetCACert: spec.TargetInfo.CACert, 740 TargetAuthTag: spec.TargetInfo.AuthTag.String(), 741 TargetPassword: spec.TargetInfo.Password, 742 TargetMacaroons: macsJSON, 743 ModelUsers: userDocs, 744 } 745 746 statusDoc = modelMigStatusDoc{ 747 Id: id, 748 StartTime: now, 749 Phase: migration.QUIESCE.String(), 750 PhaseChangedTime: now, 751 StatusMessage: msg, 752 } 753 754 ops := append(ops, []txn.Op{{ 755 C: migrationsC, 756 Id: doc.Id, 757 Assert: txn.DocMissing, 758 Insert: &doc, 759 }, { 760 C: migrationsStatusC, 761 Id: statusDoc.Id, 762 Assert: txn.DocMissing, 763 Insert: &statusDoc, 764 }, { 765 C: migrationsActiveC, 766 Id: modelUUID, 767 Assert: txn.DocMissing, 768 Insert: bson.M{"id": doc.Id}, 769 }, { 770 C: modelsC, 771 Id: modelUUID, 772 Assert: txn.DocExists, 773 Update: bson.M{"$set": bson.M{ 774 "migration-mode": MigrationModeExporting, 775 }}, 776 }, model.assertActiveOp(), 777 }...) 778 return ops, nil 779 } 780 if err := st.db().Run(buildTxn); err != nil { 781 return nil, errors.Annotate(err, "failed to create migration") 782 } 783 784 return &modelMigration{ 785 doc: doc, 786 statusDoc: statusDoc, 787 st: st, 788 }, nil 789 } 790 791 func modelUserDocs(m *Model) ([]modelMigUserDoc, error) { 792 users, err := m.Users() 793 if err != nil { 794 return nil, err 795 } 796 797 var docs []modelMigUserDoc 798 for _, user := range users { 799 docs = append(docs, modelMigUserDoc{ 800 UserID: user.UserTag.Id(), 801 Access: user.Access, 802 }) 803 } 804 805 return docs, nil 806 } 807 808 func macaroonsToJSON(m []macaroon.Slice) (string, error) { 809 if len(m) == 0 { 810 return "", nil 811 } 812 j, err := json.Marshal(m) 813 if err != nil { 814 return "", errors.Annotate(err, "marshalling macaroons") 815 } 816 return string(j), nil 817 } 818 819 func jsonToMacaroons(raw string) ([]macaroon.Slice, error) { 820 if raw == "" { 821 return nil, nil 822 } 823 var macs []macaroon.Slice 824 if err := json.Unmarshal([]byte(raw), &macs); err != nil { 825 return nil, errors.Annotate(err, "unmarshalling macaroon") 826 } 827 return macs, nil 828 } 829 830 func checkTargetController(st *State, targetControllerTag names.ControllerTag) error { 831 if targetControllerTag.Id() == st.ControllerUUID() { 832 return errors.New("model already attached to target controller") 833 } 834 return nil 835 } 836 837 // LatestMigration returns the most recent ModelMigration (if any) for a model 838 // that has not been removed from the state. Callers interested in 839 // ModelMigrations for models that have been removed after a successful 840 // migration to another controller should use CompletedMigration 841 // instead. 842 func (st *State) LatestMigration() (ModelMigration, error) { 843 mig, phase, err := st.latestMigration(st.ModelUUID()) 844 if err != nil { 845 return nil, err 846 } 847 848 // Hide previous migrations for models which have been migrated 849 // away from a model and then migrated back. 850 if phase == migration.DONE { 851 model, err := st.Model() 852 if err != nil { 853 return nil, errors.Trace(err) 854 } 855 if model.MigrationMode() == MigrationModeNone { 856 return nil, errors.NotFoundf("migration") 857 } 858 } 859 860 return mig, nil 861 } 862 863 // CompletedMigration returns the most recent migration for this state's 864 // model if it reached the DONE phase and caused the model to be relocated. 865 func (st *State) CompletedMigration() (ModelMigration, error) { 866 mig, err := st.CompletedMigrationForModel(st.ModelUUID()) 867 return mig, errors.Trace(err) 868 } 869 870 // CompletedMigrationForModel returns the most recent migration for the 871 // input model UUID if it reached the DONE phase and caused the model 872 // to be relocated. 873 func (st *State) CompletedMigrationForModel(modelUUID string) (ModelMigration, error) { 874 mig, phase, err := st.latestMigration(modelUUID) 875 if err != nil { 876 return nil, errors.Trace(err) 877 } 878 // Return NotFound if the model still modelExists or the migration is not 879 // flagged as completed. 880 modelExists, err := st.ModelExists(modelUUID) 881 if err != nil { 882 return nil, errors.Trace(err) 883 } 884 if phase != migration.DONE || modelExists { 885 return nil, errors.NotFoundf("migration") 886 } 887 888 return mig, nil 889 } 890 891 // latestMigration returns the most recent ModelMigration for a model 892 // (if any). 893 func (st *State) latestMigration(modelUUID string) (ModelMigration, migration.Phase, error) { 894 migColl, closer := st.db().GetCollection(migrationsC) 895 defer closer() 896 query := migColl.Find(bson.M{"model-uuid": modelUUID}) 897 query = query.Sort("-attempt").Limit(1) 898 mig, err := st.migrationFromQuery(query) 899 if err != nil { 900 return nil, migration.UNKNOWN, errors.Trace(err) 901 } 902 903 // Hide previous migrations for models which have been migrated 904 // away from a model and then migrated back. 905 phase, err := mig.Phase() 906 if err != nil { 907 return nil, migration.UNKNOWN, errors.Trace(err) 908 } 909 return mig, phase, nil 910 } 911 912 // Migration retrieves a specific ModelMigration by its id. See also 913 // LatestMigration and LatestCompletedMigration. 914 func (st *State) Migration(id string) (ModelMigration, error) { 915 migColl, closer := st.db().GetCollection(migrationsC) 916 defer closer() 917 mig, err := st.migrationFromQuery(migColl.FindId(id)) 918 if err != nil { 919 return nil, errors.Trace(err) 920 } 921 return mig, nil 922 } 923 924 func (st *State) migrationFromQuery(query mongo.Query) (ModelMigration, error) { 925 var doc modelMigDoc 926 err := query.One(&doc) 927 if err == mgo.ErrNotFound { 928 return nil, errors.NotFoundf("migration") 929 } else if err != nil { 930 return nil, errors.Annotate(err, "migration lookup failed") 931 } 932 933 statusColl, closer := st.db().GetCollection(migrationsStatusC) 934 defer closer() 935 var statusDoc modelMigStatusDoc 936 err = statusColl.FindId(doc.Id).One(&statusDoc) 937 if err == mgo.ErrNotFound { 938 return nil, errors.NotFoundf("migration status") 939 } else if err != nil { 940 return nil, errors.Annotate(err, "migration status lookup failed") 941 } 942 943 return &modelMigration{ 944 doc: doc, 945 statusDoc: statusDoc, 946 st: st, 947 }, nil 948 } 949 950 // IsMigrationActive returns true if a migration is in progress for 951 // the model associated with the State. 952 func (st *State) IsMigrationActive() (bool, error) { 953 return IsMigrationActive(st, st.ModelUUID()) 954 } 955 956 // IsMigrationActive returns true if a migration is in progress for 957 // the model with the given UUID. The State provided need not be for 958 // the model in question. 959 func IsMigrationActive(st *State, modelUUID string) (bool, error) { 960 active, closer := st.db().GetCollection(migrationsActiveC) 961 defer closer() 962 n, err := active.FindId(modelUUID).Count() 963 if err != nil { 964 return false, errors.Trace(err) 965 } 966 return n > 0, nil 967 } 968 969 func unixNanoToTime0(i int64) time.Time { 970 if i == 0 { 971 return time.Time{} 972 } 973 return time.Unix(0, i) 974 } 975 976 func agentTagToGlobalKey(tag names.Tag) (string, error) { 977 switch t := tag.(type) { 978 case names.MachineTag: 979 return machineGlobalKey(t.Id()), nil 980 case names.UnitTag: 981 return unitAgentGlobalKey(t.Id()), nil 982 case names.ApplicationTag: 983 return applicationGlobalKey(t.Id()), nil 984 default: 985 return "", errors.Errorf("%s is not an agent tag", tag) 986 } 987 } 988 989 func globalKeyToAgentTag(key string) (names.Tag, error) { 990 parts := strings.SplitN(key, "#", 2) 991 if len(parts) != 2 { 992 return nil, errors.NotValidf("global key %q", key) 993 } 994 keyType, keyId := parts[0], parts[1] 995 switch keyType { 996 case "m": 997 return names.NewMachineTag(keyId), nil 998 case "u": 999 return names.NewUnitTag(keyId), nil 1000 case "a": 1001 return names.NewApplicationTag(keyId), nil 1002 default: 1003 return nil, errors.NotValidf("global key type %q", keyType) 1004 } 1005 }