github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "strconv" 10 "strings" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/utils/set" 15 "gopkg.in/juju/names.v2" 16 "gopkg.in/macaroon.v1" 17 "gopkg.in/mgo.v2" 18 "gopkg.in/mgo.v2/bson" 19 "gopkg.in/mgo.v2/txn" 20 21 "github.com/juju/juju/core/migration" 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 // ExternalControl returns true if the model migration should be 38 // managed by an external process. 39 ExternalControl() bool 40 41 // Attempt returns the migration attempt identifier. This 42 // increments for each migration attempt for the model. 43 Attempt() (int, error) 44 45 // StartTime returns the time when the migration was started. 46 StartTime() time.Time 47 48 // SuccessTime returns the time when the migration reached 49 // SUCCESS. 50 SuccessTime() time.Time 51 52 // EndTime returns the time when the migration reached DONE or 53 // REAPFAILED. 54 EndTime() time.Time 55 56 // Phase returns the migration's phase. 57 Phase() (migration.Phase, error) 58 59 // PhaseChangedTime returns the time when the migration's phase 60 // last changed. 61 PhaseChangedTime() time.Time 62 63 // StatusMessage returns human readable text about the current 64 // progress of the migration. 65 StatusMessage() string 66 67 // InitiatedBy returns username the initiated the migration. 68 InitiatedBy() string 69 70 // TargetInfo returns the details required to connect to the 71 // migration's target controller. 72 TargetInfo() (*migration.TargetInfo, error) 73 74 // SetPhase sets the phase of the migration. An error will be 75 // returned if the new phase does not follow the current phase or 76 // if the migration is no longer active. 77 SetPhase(nextPhase migration.Phase) error 78 79 // SetStatusMessage sets some human readable text about the 80 // current progress of the migration. 81 SetStatusMessage(text string) error 82 83 // SubmitMinionReport records a report from a migration minion 84 // worker about the success or failure to complete its actions for 85 // a given migration phase. 86 SubmitMinionReport(tag names.Tag, phase migration.Phase, success bool) error 87 88 // MinionReports returns details of the minions that have reported 89 // success or failure for the current migration phase, as well as 90 // those which are yet to report. 91 MinionReports() (*MinionReports, error) 92 93 // WatchMinionReports returns a notify watcher which triggers when 94 // a migration minion has reported back about the success or failure 95 // of its actions for the current migration phase. 96 WatchMinionReports() (NotifyWatcher, error) 97 98 // Refresh updates the contents of the ModelMigration from the 99 // underlying state. 100 Refresh() error 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 // InitiatedBy holds the username of the user that triggered the 131 // migration. It should be in "user@domain" format. 132 InitiatedBy string `bson:"initiated-by"` 133 134 // ExternalControl is true if the migration will be controlled by 135 // an external process, instead of the migrationmaster worker. 136 ExternalControl bool `bson:"external-control"` 137 138 // TargetController holds the UUID of the target controller. 139 TargetController string `bson:"target-controller"` 140 141 // TargetAddrs holds the host:port values for the target API 142 // server. 143 TargetAddrs []string `bson:"target-addrs"` 144 145 // TargetCACert holds the certificate to validate the target API 146 // server's TLS certificate. 147 TargetCACert string `bson:"target-cacert"` 148 149 // TargetAuthTag holds a string representation of the tag to 150 // authenticate to the target controller with. 151 TargetAuthTag string `bson:"target-entity"` 152 153 // TargetPassword holds the password to use with TargetAuthTag 154 // when authenticating. 155 TargetPassword string `bson:"target-password,omitempty"` 156 157 // TargetMacaroons holds the macaroons to use with TargetAuthTag 158 // when authenticating. 159 TargetMacaroons string `bson:"target-macaroons,omitempty"` 160 } 161 162 // modelMigStatusDoc tracks the progress of a migration attempt for a 163 // model. These are written into migrationsStatusC. 164 // 165 // There is exactly one document in migrationsStatusC for each 166 // document in migrationsC. Separating them allows for watching 167 // for new model migrations without being woken up for each model 168 // migration status change. 169 type modelMigStatusDoc struct { 170 // These are the same as the ids as migrationsC. 171 // "uuid:sequence". 172 Id string `bson:"_id"` 173 174 // StartTime holds the time the migration started (stored as per 175 // UnixNano). 176 StartTime int64 `bson:"start-time"` 177 178 // StartTime holds the time the migration reached the SUCCESS 179 // phase (stored as per UnixNano). 180 SuccessTime int64 `bson:"success-time"` 181 182 // EndTime holds the time the migration reached a terminal (end) 183 // phase (stored as per UnixNano). 184 EndTime int64 `bson:"end-time"` 185 186 // Phase holds the current migration phase. This should be one of 187 // the string representations of the core/migrations.Phase 188 // constants. 189 Phase string `bson:"phase"` 190 191 // PhaseChangedTime holds the time that Phase last changed (stored 192 // as per UnixNano). 193 PhaseChangedTime int64 `bson:"phase-changed-time"` 194 195 // StatusMessage holds a human readable message about the 196 // migration's progress. 197 StatusMessage string `bson:"status-message"` 198 } 199 200 type modelMigMinionSyncDoc struct { 201 Id string `bson:"_id"` 202 MigrationId string `bson:"migration-id"` 203 Phase string `bson:"phase"` 204 EntityKey string `bson:"entity-key"` 205 Time int64 `bson:"time"` 206 Success bool `bson:"success"` 207 } 208 209 // Id implements ModelMigration. 210 func (mig *modelMigration) Id() string { 211 return mig.doc.Id 212 } 213 214 // ModelUUID implements ModelMigration. 215 func (mig *modelMigration) ModelUUID() string { 216 return mig.doc.ModelUUID 217 } 218 219 // ExternalControl implements ModelMigration. 220 func (mig *modelMigration) ExternalControl() bool { 221 return mig.doc.ExternalControl 222 } 223 224 // Attempt implements ModelMigration. 225 func (mig *modelMigration) Attempt() (int, error) { 226 attempt, err := strconv.Atoi(mig.st.localID(mig.doc.Id)) 227 if err != nil { 228 // This really shouldn't happen. 229 return -1, errors.Errorf("invalid migration id: %v", mig.doc.Id) 230 } 231 return attempt, nil 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 Addrs: mig.doc.TargetAddrs, 286 CACert: mig.doc.TargetCACert, 287 AuthTag: authTag, 288 Password: mig.doc.TargetPassword, 289 Macaroons: macs, 290 }, nil 291 } 292 293 // SetPhase implements ModelMigration. 294 func (mig *modelMigration) SetPhase(nextPhase migration.Phase) error { 295 now := mig.st.clock.Now().UnixNano() 296 297 phase, err := mig.Phase() 298 if err != nil { 299 return errors.Trace(err) 300 } 301 302 if nextPhase == phase { 303 return nil // Already at that phase. Nothing to do. 304 } 305 if !phase.CanTransitionTo(nextPhase) { 306 return errors.Errorf("illegal phase change: %s -> %s", phase, nextPhase) 307 } 308 309 nextDoc := mig.statusDoc 310 nextDoc.Phase = nextPhase.String() 311 nextDoc.PhaseChangedTime = now 312 update := bson.M{ 313 "phase": nextDoc.Phase, 314 "phase-changed-time": now, 315 } 316 if nextPhase == migration.SUCCESS { 317 nextDoc.SuccessTime = now 318 update["success-time"] = now 319 } 320 var ops []txn.Op 321 322 // If the migration aborted, make the model active again. 323 if nextPhase == migration.ABORTDONE { 324 ops = append(ops, txn.Op{ 325 C: modelsC, 326 Id: mig.doc.ModelUUID, 327 Assert: txn.DocExists, 328 Update: bson.M{ 329 "$set": bson.M{"migration-mode": MigrationModeNone}, 330 }, 331 }) 332 } 333 334 // Set end timestamps and mark migration as no longer active if a 335 // terminal phase is hit. 336 if nextPhase.IsTerminal() { 337 nextDoc.EndTime = now 338 update["end-time"] = now 339 ops = append(ops, txn.Op{ 340 C: migrationsActiveC, 341 Id: mig.doc.ModelUUID, 342 Assert: txn.DocExists, 343 Remove: true, 344 }) 345 } 346 347 ops = append(ops, txn.Op{ 348 C: migrationsStatusC, 349 Id: mig.statusDoc.Id, 350 Update: bson.M{"$set": update}, 351 // Ensure phase hasn't changed underneath us 352 Assert: bson.M{"phase": mig.statusDoc.Phase}, 353 }) 354 355 if err := mig.st.runTransaction(ops); err == txn.ErrAborted { 356 return errors.New("phase already changed") 357 } else if err != nil { 358 return errors.Annotate(err, "failed to update phase") 359 } 360 361 mig.statusDoc = nextDoc 362 return nil 363 } 364 365 // SetStatusMessage implements ModelMigration. 366 func (mig *modelMigration) SetStatusMessage(text string) error { 367 ops := []txn.Op{{ 368 C: migrationsStatusC, 369 Id: mig.statusDoc.Id, 370 Update: bson.M{"$set": bson.M{"status-message": text}}, 371 Assert: txn.DocExists, 372 }} 373 if err := mig.st.runTransaction(ops); err != nil { 374 return errors.Annotate(err, "failed to set migration status") 375 } 376 mig.statusDoc.StatusMessage = text 377 return nil 378 } 379 380 // SubmitMinionReport implements ModelMigration. 381 func (mig *modelMigration) SubmitMinionReport(tag names.Tag, phase migration.Phase, success bool) error { 382 globalKey, err := agentTagToGlobalKey(tag) 383 if err != nil { 384 return errors.Trace(err) 385 } 386 docID := mig.minionReportId(phase, globalKey) 387 doc := modelMigMinionSyncDoc{ 388 Id: docID, 389 MigrationId: mig.Id(), 390 Phase: phase.String(), 391 EntityKey: globalKey, 392 Time: mig.st.clock.Now().UnixNano(), 393 Success: success, 394 } 395 ops := []txn.Op{{ 396 C: migrationsMinionSyncC, 397 Id: docID, 398 Insert: doc, 399 Assert: txn.DocMissing, 400 }} 401 err = mig.st.runTransaction(ops) 402 if errors.Cause(err) == txn.ErrAborted { 403 coll, closer := mig.st.getCollection(migrationsMinionSyncC) 404 defer closer() 405 var existingDoc modelMigMinionSyncDoc 406 err := coll.FindId(docID).Select(bson.M{"success": 1}).One(&existingDoc) 407 if err != nil { 408 return errors.Annotate(err, "checking existing report") 409 } 410 if existingDoc.Success != success { 411 return errors.Errorf("conflicting reports received for %s/%s/%s", 412 mig.Id(), phase.String(), tag) 413 } 414 return nil 415 } else if err != nil { 416 return errors.Trace(err) 417 } 418 return nil 419 } 420 421 // MinionReports implements ModelMigration. 422 func (mig *modelMigration) MinionReports() (*MinionReports, error) { 423 all, err := mig.getAllAgents() 424 if err != nil { 425 return nil, errors.Trace(err) 426 } 427 428 phase, err := mig.Phase() 429 if err != nil { 430 return nil, errors.Annotate(err, "retrieving phase") 431 } 432 433 coll, closer := mig.st.getCollection(migrationsMinionSyncC) 434 defer closer() 435 query := coll.Find(bson.M{"_id": bson.M{ 436 "$regex": "^" + mig.minionReportId(phase, ".+"), 437 }}) 438 query = query.Select(bson.M{ 439 "entity-key": 1, 440 "success": 1, 441 }) 442 var docs []bson.M 443 if err := query.All(&docs); err != nil { 444 return nil, errors.Annotate(err, "retrieving minion reports") 445 } 446 447 succeeded := set.NewTags() 448 failed := set.NewTags() 449 for _, doc := range docs { 450 entityKey, ok := doc["entity-key"].(string) 451 if !ok { 452 return nil, errors.Errorf("unexpected entity-key %v", doc["entity-key"]) 453 } 454 tag, err := globalKeyToAgentTag(entityKey) 455 if err != nil { 456 return nil, errors.Trace(err) 457 } 458 success, ok := doc["success"].(bool) 459 if !ok { 460 return nil, errors.Errorf("unexpected success value: %v", doc["success"]) 461 } 462 if success { 463 succeeded.Add(tag) 464 } else { 465 failed.Add(tag) 466 } 467 } 468 469 unknown := all.Difference(succeeded).Difference(failed) 470 471 return &MinionReports{ 472 Succeeded: succeeded.Values(), 473 Failed: failed.Values(), 474 Unknown: unknown.Values(), 475 }, nil 476 } 477 478 // WatchMinionReports implements ModelMigration. 479 func (mig *modelMigration) WatchMinionReports() (NotifyWatcher, error) { 480 phase, err := mig.Phase() 481 if err != nil { 482 return nil, errors.Annotate(err, "retrieving phase") 483 } 484 prefix := mig.minionReportId(phase, "") 485 filter := func(rawId interface{}) bool { 486 id, ok := rawId.(string) 487 if !ok { 488 return false 489 } 490 return strings.HasPrefix(id, prefix) 491 } 492 return newNotifyCollWatcher(mig.st, migrationsMinionSyncC, filter), nil 493 } 494 495 func (mig *modelMigration) minionReportId(phase migration.Phase, globalKey string) string { 496 return fmt.Sprintf("%s:%s:%s", mig.Id(), phase.String(), globalKey) 497 } 498 499 func (mig *modelMigration) getAllAgents() (set.Tags, error) { 500 machineTags, err := mig.loadAgentTags(machinesC, "machineid", 501 func(id string) names.Tag { return names.NewMachineTag(id) }, 502 ) 503 if err != nil { 504 return nil, errors.Annotate(err, "loading machine tags") 505 } 506 507 unitTags, err := mig.loadAgentTags(unitsC, "name", 508 func(name string) names.Tag { return names.NewUnitTag(name) }, 509 ) 510 if err != nil { 511 return nil, errors.Annotate(err, "loading unit names") 512 } 513 514 return machineTags.Union(unitTags), nil 515 } 516 517 func (mig *modelMigration) loadAgentTags(collName, fieldName string, convert func(string) names.Tag) ( 518 set.Tags, error, 519 ) { 520 // During migrations we know that nothing there are no machines or 521 // units being provisioned or destroyed so a simple query of the 522 // collections will do. 523 coll, closer := mig.st.getCollection(collName) 524 defer closer() 525 var docs []bson.M 526 err := coll.Find(nil).Select(bson.M{fieldName: 1}).All(&docs) 527 if err != nil { 528 return nil, errors.Trace(err) 529 } 530 531 out := set.NewTags() 532 for _, doc := range docs { 533 v, ok := doc[fieldName].(string) 534 if !ok { 535 return nil, errors.Errorf("invalid %s value: %v", fieldName, doc[fieldName]) 536 } 537 out.Add(convert(v)) 538 } 539 return out, nil 540 } 541 542 // Refresh implements ModelMigration. 543 func (mig *modelMigration) Refresh() error { 544 // Only the status document is updated. The modelMigDoc is static 545 // after creation. 546 statusColl, closer := mig.st.getCollection(migrationsStatusC) 547 defer closer() 548 var statusDoc modelMigStatusDoc 549 err := statusColl.FindId(mig.doc.Id).One(&statusDoc) 550 if err == mgo.ErrNotFound { 551 return errors.NotFoundf("migration status") 552 } else if err != nil { 553 return errors.Annotate(err, "migration status lookup failed") 554 } 555 556 mig.statusDoc = statusDoc 557 return nil 558 } 559 560 // MigrationSpec holds the information required to create a 561 // ModelMigration instance. 562 type MigrationSpec struct { 563 InitiatedBy names.UserTag 564 TargetInfo migration.TargetInfo 565 ExternalControl bool 566 } 567 568 // Validate returns an error if the MigrationSpec contains bad 569 // data. Nil is returned otherwise. 570 func (spec *MigrationSpec) Validate() error { 571 if !names.IsValidUser(spec.InitiatedBy.Id()) { 572 return errors.NotValidf("InitiatedBy") 573 } 574 return spec.TargetInfo.Validate() 575 } 576 577 // CreateMigration initialises state that tracks a model migration. It 578 // will return an error if there is already a model migration in 579 // progress. 580 func (st *State) CreateMigration(spec MigrationSpec) (ModelMigration, error) { 581 if st.IsController() { 582 return nil, errors.New("controllers can't be migrated") 583 } 584 if err := spec.Validate(); err != nil { 585 return nil, errors.Trace(err) 586 } 587 if err := checkTargetController(st, spec.TargetInfo.ControllerTag); err != nil { 588 return nil, errors.Trace(err) 589 } 590 591 now := st.clock.Now().UnixNano() 592 modelUUID := st.ModelUUID() 593 var doc modelMigDoc 594 var statusDoc modelMigStatusDoc 595 buildTxn := func(int) ([]txn.Op, error) { 596 model, err := st.Model() 597 if err != nil { 598 return nil, errors.Annotate(err, "failed to load model") 599 } 600 if model.Life() != Alive { 601 return nil, errors.New("model is not alive") 602 } 603 604 if isActive, err := st.IsMigrationActive(); err != nil { 605 return nil, errors.Trace(err) 606 } else if isActive { 607 return nil, errors.New("already in progress") 608 } 609 610 macsJSON, err := macaroonsToJSON(spec.TargetInfo.Macaroons) 611 if err != nil { 612 return nil, errors.Trace(err) 613 } 614 615 seq, err := st.sequence("modelmigration") 616 if err != nil { 617 return nil, errors.Trace(err) 618 } 619 620 id := fmt.Sprintf("%s:%d", modelUUID, seq) 621 doc = modelMigDoc{ 622 Id: id, 623 ModelUUID: modelUUID, 624 InitiatedBy: spec.InitiatedBy.Id(), 625 ExternalControl: spec.ExternalControl, 626 TargetController: spec.TargetInfo.ControllerTag.Id(), 627 TargetAddrs: spec.TargetInfo.Addrs, 628 TargetCACert: spec.TargetInfo.CACert, 629 TargetAuthTag: spec.TargetInfo.AuthTag.String(), 630 TargetPassword: spec.TargetInfo.Password, 631 TargetMacaroons: macsJSON, 632 } 633 statusDoc = modelMigStatusDoc{ 634 Id: id, 635 StartTime: now, 636 Phase: migration.QUIESCE.String(), 637 PhaseChangedTime: now, 638 StatusMessage: "starting", 639 } 640 return []txn.Op{{ 641 C: migrationsC, 642 Id: doc.Id, 643 Assert: txn.DocMissing, 644 Insert: &doc, 645 }, { 646 C: migrationsStatusC, 647 Id: statusDoc.Id, 648 Assert: txn.DocMissing, 649 Insert: &statusDoc, 650 }, { 651 C: migrationsActiveC, 652 Id: modelUUID, 653 Assert: txn.DocMissing, 654 Insert: bson.M{"id": doc.Id}, 655 }, { 656 C: modelsC, 657 Id: modelUUID, 658 Assert: txn.DocExists, 659 Update: bson.M{"$set": bson.M{ 660 "migration-mode": MigrationModeExporting, 661 }}, 662 }, model.assertActiveOp(), 663 }, nil 664 } 665 if err := st.run(buildTxn); err != nil { 666 return nil, errors.Annotate(err, "failed to create migration") 667 } 668 669 return &modelMigration{ 670 doc: doc, 671 statusDoc: statusDoc, 672 st: st, 673 }, nil 674 } 675 676 func macaroonsToJSON(m []macaroon.Slice) (string, error) { 677 if len(m) == 0 { 678 return "", nil 679 } 680 j, err := json.Marshal(m) 681 if err != nil { 682 return "", errors.Annotate(err, "marshalling macaroons") 683 } 684 return string(j), nil 685 } 686 687 func jsonToMacaroons(raw string) ([]macaroon.Slice, error) { 688 if raw == "" { 689 return nil, nil 690 } 691 var macs []macaroon.Slice 692 if err := json.Unmarshal([]byte(raw), &macs); err != nil { 693 return nil, errors.Annotate(err, "unmarshalling macaroon") 694 } 695 return macs, nil 696 } 697 698 func checkTargetController(st *State, targetControllerTag names.ControllerTag) error { 699 currentController, err := st.ControllerModel() 700 if err != nil { 701 return errors.Annotate(err, "failed to load existing controller model") 702 } 703 if targetControllerTag == currentController.ControllerTag() { 704 return errors.New("model already attached to target controller") 705 } 706 return nil 707 } 708 709 // LatestMigration returns the most recent ModelMigration for a model 710 // (if any). 711 func (st *State) LatestMigration() (ModelMigration, error) { 712 migColl, closer := st.getCollection(migrationsC) 713 defer closer() 714 query := migColl.Find(bson.M{"model-uuid": st.ModelUUID()}) 715 query = query.Sort("-_id").Limit(1) 716 mig, err := st.migrationFromQuery(query) 717 if err != nil { 718 return nil, errors.Trace(err) 719 } 720 721 // Hide previous migrations for models which have been migrated 722 // away from a model and then migrated back. 723 phase, err := mig.Phase() 724 if err != nil { 725 return nil, errors.Trace(err) 726 } 727 if phase == migration.DONE { 728 model, err := st.Model() 729 if err != nil { 730 return nil, errors.Trace(err) 731 } 732 if model.MigrationMode() == MigrationModeNone { 733 return nil, errors.NotFoundf("migration") 734 } 735 } 736 737 return mig, nil 738 } 739 740 // Migration retrieves a specific ModelMigration by its id. See also 741 // LatestMigration. 742 func (st *State) Migration(id string) (ModelMigration, error) { 743 migColl, closer := st.getCollection(migrationsC) 744 defer closer() 745 mig, err := st.migrationFromQuery(migColl.FindId(id)) 746 if err != nil { 747 return nil, errors.Trace(err) 748 } 749 return mig, nil 750 } 751 752 func (st *State) migrationFromQuery(query mongo.Query) (ModelMigration, error) { 753 var doc modelMigDoc 754 err := query.One(&doc) 755 if err == mgo.ErrNotFound { 756 return nil, errors.NotFoundf("migration") 757 } else if err != nil { 758 return nil, errors.Annotate(err, "migration lookup failed") 759 } 760 761 statusColl, closer := st.getCollection(migrationsStatusC) 762 defer closer() 763 var statusDoc modelMigStatusDoc 764 err = statusColl.FindId(doc.Id).One(&statusDoc) 765 if err == mgo.ErrNotFound { 766 return nil, errors.NotFoundf("migration status") 767 } else if err != nil { 768 return nil, errors.Annotate(err, "migration status lookup failed") 769 } 770 771 return &modelMigration{ 772 doc: doc, 773 statusDoc: statusDoc, 774 st: st, 775 }, nil 776 } 777 778 // IsMigrationActive returns true if a migration is in progress for 779 // the model associated with the State. 780 func (st *State) IsMigrationActive() (bool, error) { 781 return IsMigrationActive(st, st.ModelUUID()) 782 } 783 784 // IsMigrationActive returns true if a migration is in progress for 785 // the model with the given UUID. The State provided need not be for 786 // the model in question. 787 func IsMigrationActive(st *State, modelUUID string) (bool, error) { 788 active, closer := st.getCollection(migrationsActiveC) 789 defer closer() 790 n, err := active.FindId(modelUUID).Count() 791 if err != nil { 792 return false, errors.Trace(err) 793 } 794 return n > 0, nil 795 } 796 797 func unixNanoToTime0(i int64) time.Time { 798 if i == 0 { 799 return time.Time{} 800 } 801 return time.Unix(0, i) 802 } 803 804 func agentTagToGlobalKey(tag names.Tag) (string, error) { 805 switch t := tag.(type) { 806 case names.MachineTag: 807 return machineGlobalKey(t.Id()), nil 808 case names.UnitTag: 809 return unitAgentGlobalKey(t.Id()), nil 810 default: 811 return "", errors.Errorf("%s is not an agent tag", tag) 812 } 813 } 814 815 func globalKeyToAgentTag(key string) (names.Tag, error) { 816 parts := strings.SplitN(key, "#", 2) 817 if len(parts) != 2 { 818 return nil, errors.NotValidf("global key %q", key) 819 } 820 keyType, keyId := parts[0], parts[1] 821 switch keyType { 822 case "m": 823 return names.NewMachineTag(keyId), nil 824 case "u": 825 return names.NewUnitTag(keyId), nil 826 default: 827 return nil, errors.NotValidf("global key type %q", keyType) 828 } 829 }