github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/state/migration_export.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 "strings" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/names" 13 "github.com/juju/utils/set" 14 "gopkg.in/mgo.v2/bson" 15 16 "github.com/juju/juju/core/description" 17 ) 18 19 // Export the current model for the State. 20 func (st *State) Export() (description.Model, error) { 21 dbModel, err := st.Model() 22 if err != nil { 23 return nil, errors.Trace(err) 24 } 25 26 export := exporter{ 27 st: st, 28 dbModel: dbModel, 29 logger: loggo.GetLogger("juju.state.export-model"), 30 } 31 if err := export.readAllStatuses(); err != nil { 32 return nil, errors.Annotate(err, "reading statuses") 33 } 34 if err := export.readAllStatusHistory(); err != nil { 35 return nil, errors.Trace(err) 36 } 37 if err := export.readAllSettings(); err != nil { 38 return nil, errors.Trace(err) 39 } 40 if err := export.readAllAnnotations(); err != nil { 41 return nil, errors.Trace(err) 42 } 43 if err := export.readAllConstraints(); err != nil { 44 return nil, errors.Trace(err) 45 } 46 47 envConfig, found := export.settings[modelGlobalKey] 48 if !found { 49 return nil, errors.New("missing environ config") 50 } 51 52 blocks, err := export.readBlocks() 53 if err != nil { 54 return nil, errors.Trace(err) 55 } 56 57 args := description.ModelArgs{ 58 Owner: dbModel.Owner(), 59 Config: envConfig.Settings, 60 LatestToolsVersion: dbModel.LatestToolsVersion(), 61 Blocks: blocks, 62 } 63 export.model = description.NewModel(args) 64 modelKey := dbModel.globalKey() 65 export.model.SetAnnotations(export.getAnnotations(modelKey)) 66 if err := export.sequences(); err != nil { 67 return nil, errors.Trace(err) 68 } 69 constraintsArgs, err := export.constraintsArgs(modelKey) 70 if err != nil { 71 return nil, errors.Trace(err) 72 } 73 export.model.SetConstraints(constraintsArgs) 74 75 if err := export.modelUsers(); err != nil { 76 return nil, errors.Trace(err) 77 } 78 if err := export.machines(); err != nil { 79 return nil, errors.Trace(err) 80 } 81 if err := export.services(); err != nil { 82 return nil, errors.Trace(err) 83 } 84 if err := export.relations(); err != nil { 85 return nil, errors.Trace(err) 86 } 87 88 if err := export.model.Validate(); err != nil { 89 return nil, errors.Trace(err) 90 } 91 92 export.logExtras() 93 94 return export.model, nil 95 } 96 97 type exporter struct { 98 st *State 99 dbModel *Model 100 model description.Model 101 logger loggo.Logger 102 103 annotations map[string]annotatorDoc 104 constraints map[string]bson.M 105 settings map[string]settingsDoc 106 status map[string]bson.M 107 statusHistory map[string][]historicalStatusDoc 108 // Map of service name to units. Populated as part 109 // of the services export. 110 units map[string][]*Unit 111 } 112 113 func (e *exporter) sequences() error { 114 sequences, closer := e.st.getCollection(sequenceC) 115 defer closer() 116 117 var docs []sequenceDoc 118 if err := sequences.Find(nil).All(&docs); err != nil { 119 return errors.Trace(err) 120 } 121 122 for _, doc := range docs { 123 e.model.SetSequence(doc.Name, doc.Counter) 124 } 125 return nil 126 } 127 128 func (e *exporter) readBlocks() (map[string]string, error) { 129 blocks, closer := e.st.getCollection(blocksC) 130 defer closer() 131 132 var docs []blockDoc 133 if err := blocks.Find(nil).All(&docs); err != nil { 134 return nil, errors.Trace(err) 135 } 136 137 result := make(map[string]string) 138 for _, doc := range docs { 139 // We don't care about the id, uuid, or tag. 140 // The uuid and tag both refer to the model uuid, and the 141 // id is opaque - even though it is sequence generated. 142 result[doc.Type.MigrationValue()] = doc.Message 143 } 144 return result, nil 145 } 146 147 func (e *exporter) modelUsers() error { 148 users, err := e.dbModel.Users() 149 if err != nil { 150 return errors.Trace(err) 151 } 152 lastConnections, err := e.readLastConnectionTimes() 153 if err != nil { 154 return errors.Trace(err) 155 } 156 157 for _, user := range users { 158 lastConn := lastConnections[strings.ToLower(user.UserName())] 159 arg := description.UserArgs{ 160 Name: user.UserTag(), 161 DisplayName: user.DisplayName(), 162 CreatedBy: names.NewUserTag(user.CreatedBy()), 163 DateCreated: user.DateCreated(), 164 LastConnection: lastConn, 165 ReadOnly: user.ReadOnly(), 166 } 167 e.model.AddUser(arg) 168 } 169 return nil 170 } 171 172 func (e *exporter) machines() error { 173 machines, err := e.st.AllMachines() 174 if err != nil { 175 return errors.Trace(err) 176 } 177 e.logger.Debugf("found %d machines", len(machines)) 178 179 instanceDataCollection, closer := e.st.getCollection(instanceDataC) 180 defer closer() 181 182 var instData []instanceData 183 instances := make(map[string]instanceData) 184 if err := instanceDataCollection.Find(nil).All(&instData); err != nil { 185 return errors.Annotate(err, "instance data") 186 } 187 e.logger.Debugf("found %d instanceData", len(instData)) 188 for _, data := range instData { 189 instances[data.MachineId] = data 190 } 191 192 // Read all the open ports documents. 193 openedPorts, closer := e.st.getCollection(openedPortsC) 194 defer closer() 195 var portsData []portsDoc 196 if err := openedPorts.Find(nil).All(&portsData); err != nil { 197 return errors.Annotate(err, "opened ports") 198 } 199 e.logger.Debugf("found %d openedPorts docs", len(portsData)) 200 201 // We are iterating through a flat list of machines, but the migration 202 // model stores the nesting. The AllMachines method assures us that the 203 // machines are returned in an order so the parent will always before 204 // any children. 205 machineMap := make(map[string]description.Machine) 206 207 for _, machine := range machines { 208 e.logger.Debugf("export machine %s", machine.Id()) 209 210 var exParent description.Machine 211 if parentId := ParentId(machine.Id()); parentId != "" { 212 var found bool 213 exParent, found = machineMap[parentId] 214 if !found { 215 return errors.Errorf("machine %s missing parent", machine.Id()) 216 } 217 } 218 219 exMachine, err := e.newMachine(exParent, machine, instances, portsData) 220 if err != nil { 221 return errors.Trace(err) 222 } 223 machineMap[machine.Id()] = exMachine 224 } 225 226 return nil 227 } 228 229 func (e *exporter) newMachine(exParent description.Machine, machine *Machine, instances map[string]instanceData, portsData []portsDoc) (description.Machine, error) { 230 args := description.MachineArgs{ 231 Id: machine.MachineTag(), 232 Nonce: machine.doc.Nonce, 233 PasswordHash: machine.doc.PasswordHash, 234 Placement: machine.doc.Placement, 235 Series: machine.doc.Series, 236 ContainerType: machine.doc.ContainerType, 237 } 238 239 if supported, ok := machine.SupportedContainers(); ok { 240 containers := make([]string, len(supported)) 241 for i, containerType := range supported { 242 containers[i] = string(containerType) 243 } 244 args.SupportedContainers = &containers 245 } 246 247 for _, job := range machine.Jobs() { 248 args.Jobs = append(args.Jobs, job.MigrationValue()) 249 } 250 251 // A null value means that we don't yet know which containers 252 // are supported. An empty slice means 'no containers are supported'. 253 var exMachine description.Machine 254 if exParent == nil { 255 exMachine = e.model.AddMachine(args) 256 } else { 257 exMachine = exParent.AddContainer(args) 258 } 259 exMachine.SetAddresses( 260 e.newAddressArgsSlice(machine.doc.MachineAddresses), 261 e.newAddressArgsSlice(machine.doc.Addresses)) 262 exMachine.SetPreferredAddresses( 263 e.newAddressArgs(machine.doc.PreferredPublicAddress), 264 e.newAddressArgs(machine.doc.PreferredPrivateAddress)) 265 266 // We fully expect the machine to have tools set, and that there is 267 // some instance data. 268 instData, found := instances[machine.doc.Id] 269 if !found { 270 return nil, errors.NotValidf("missing instance data for machine %s", machine.Id()) 271 } 272 exMachine.SetInstance(e.newCloudInstanceArgs(instData)) 273 274 // Find the current machine status. 275 globalKey := machine.globalKey() 276 statusArgs, err := e.statusArgs(globalKey) 277 if err != nil { 278 return nil, errors.Annotatef(err, "status for machine %s", machine.Id()) 279 } 280 exMachine.SetStatus(statusArgs) 281 exMachine.SetStatusHistory(e.statusHistoryArgs(globalKey)) 282 283 tools, err := machine.AgentTools() 284 if err != nil { 285 // This means the tools aren't set, but they should be. 286 return nil, errors.Trace(err) 287 } 288 289 exMachine.SetTools(description.AgentToolsArgs{ 290 Version: tools.Version, 291 URL: tools.URL, 292 SHA256: tools.SHA256, 293 Size: tools.Size, 294 }) 295 296 for _, args := range e.openedPortsArgsForMachine(machine.Id(), portsData) { 297 exMachine.AddOpenedPorts(args) 298 } 299 300 exMachine.SetAnnotations(e.getAnnotations(globalKey)) 301 302 constraintsArgs, err := e.constraintsArgs(globalKey) 303 if err != nil { 304 return nil, errors.Trace(err) 305 } 306 exMachine.SetConstraints(constraintsArgs) 307 308 return exMachine, nil 309 } 310 311 func (e *exporter) openedPortsArgsForMachine(machineId string, portsData []portsDoc) []description.OpenedPortsArgs { 312 var result []description.OpenedPortsArgs 313 for _, doc := range portsData { 314 // Don't bother including a subnet if there are no ports open on it. 315 if doc.MachineID == machineId && len(doc.Ports) > 0 { 316 args := description.OpenedPortsArgs{SubnetID: doc.SubnetID} 317 for _, p := range doc.Ports { 318 args.OpenedPorts = append(args.OpenedPorts, description.PortRangeArgs{ 319 UnitName: p.UnitName, 320 FromPort: p.FromPort, 321 ToPort: p.ToPort, 322 Protocol: p.Protocol, 323 }) 324 } 325 result = append(result, args) 326 } 327 } 328 return result 329 } 330 331 func (e *exporter) newAddressArgsSlice(a []address) []description.AddressArgs { 332 result := []description.AddressArgs{} 333 for _, addr := range a { 334 result = append(result, e.newAddressArgs(addr)) 335 } 336 return result 337 } 338 339 func (e *exporter) newAddressArgs(a address) description.AddressArgs { 340 return description.AddressArgs{ 341 Value: a.Value, 342 Type: a.AddressType, 343 Scope: a.Scope, 344 Origin: a.Origin, 345 } 346 } 347 348 func (e *exporter) newCloudInstanceArgs(data instanceData) description.CloudInstanceArgs { 349 inst := description.CloudInstanceArgs{ 350 InstanceId: string(data.InstanceId), 351 Status: data.Status, 352 } 353 if data.Arch != nil { 354 inst.Architecture = *data.Arch 355 } 356 if data.Mem != nil { 357 inst.Memory = *data.Mem 358 } 359 if data.RootDisk != nil { 360 inst.RootDisk = *data.RootDisk 361 } 362 if data.CpuCores != nil { 363 inst.CpuCores = *data.CpuCores 364 } 365 if data.CpuPower != nil { 366 inst.CpuPower = *data.CpuPower 367 } 368 if data.Tags != nil { 369 inst.Tags = *data.Tags 370 } 371 if data.AvailZone != nil { 372 inst.AvailabilityZone = *data.AvailZone 373 } 374 return inst 375 } 376 377 func (e *exporter) services() error { 378 services, err := e.st.AllServices() 379 if err != nil { 380 return errors.Trace(err) 381 } 382 e.logger.Debugf("found %d services", len(services)) 383 384 refcounts, err := e.readAllSettingsRefCounts() 385 if err != nil { 386 return errors.Trace(err) 387 } 388 389 e.units, err = e.readAllUnits() 390 if err != nil { 391 return errors.Trace(err) 392 } 393 394 meterStatus, err := e.readAllMeterStatus() 395 if err != nil { 396 return errors.Trace(err) 397 } 398 399 leaders := e.readServiceLeaders() 400 401 for _, service := range services { 402 serviceUnits := e.units[service.Name()] 403 leader := leaders[service.Name()] 404 if err := e.addService(service, refcounts, serviceUnits, meterStatus, leader); err != nil { 405 return errors.Trace(err) 406 } 407 } 408 return nil 409 } 410 411 func (e *exporter) readServiceLeaders() map[string]string { 412 result := make(map[string]string) 413 for key, value := range e.st.leadershipClient.Leases() { 414 result[key] = value.Holder 415 } 416 return result 417 } 418 419 func (e *exporter) addService(service *Service, refcounts map[string]int, units []*Unit, meterStatus map[string]*meterStatusDoc, leader string) error { 420 settingsKey := service.settingsKey() 421 leadershipKey := leadershipSettingsKey(service.Name()) 422 423 serviceSettingsDoc, found := e.settings[settingsKey] 424 if !found { 425 return errors.Errorf("missing settings for service %q", service.Name()) 426 } 427 refCount, found := refcounts[settingsKey] 428 if !found { 429 return errors.Errorf("missing settings refcount for service %q", service.Name()) 430 } 431 leadershipSettingsDoc, found := e.settings[leadershipKey] 432 if !found { 433 return errors.Errorf("missing leadership settings for service %q", service.Name()) 434 } 435 436 args := description.ServiceArgs{ 437 Tag: service.ServiceTag(), 438 Series: service.doc.Series, 439 Subordinate: service.doc.Subordinate, 440 CharmURL: service.doc.CharmURL.String(), 441 Channel: service.doc.Channel, 442 CharmModifiedVersion: service.doc.CharmModifiedVersion, 443 ForceCharm: service.doc.ForceCharm, 444 Exposed: service.doc.Exposed, 445 MinUnits: service.doc.MinUnits, 446 Settings: serviceSettingsDoc.Settings, 447 SettingsRefCount: refCount, 448 Leader: leader, 449 LeadershipSettings: leadershipSettingsDoc.Settings, 450 MetricsCredentials: service.doc.MetricCredentials, 451 } 452 exService := e.model.AddService(args) 453 // Find the current service status. 454 globalKey := service.globalKey() 455 statusArgs, err := e.statusArgs(globalKey) 456 if err != nil { 457 return errors.Annotatef(err, "status for service %s", service.Name()) 458 } 459 exService.SetStatus(statusArgs) 460 exService.SetStatusHistory(e.statusHistoryArgs(globalKey)) 461 exService.SetAnnotations(e.getAnnotations(globalKey)) 462 463 constraintsArgs, err := e.constraintsArgs(globalKey) 464 if err != nil { 465 return errors.Trace(err) 466 } 467 exService.SetConstraints(constraintsArgs) 468 469 for _, unit := range units { 470 agentKey := unit.globalAgentKey() 471 unitMeterStatus, found := meterStatus[agentKey] 472 if !found { 473 return errors.Errorf("missing meter status for unit %s", unit.Name()) 474 } 475 476 args := description.UnitArgs{ 477 Tag: unit.UnitTag(), 478 Machine: names.NewMachineTag(unit.doc.MachineId), 479 PasswordHash: unit.doc.PasswordHash, 480 MeterStatusCode: unitMeterStatus.Code, 481 MeterStatusInfo: unitMeterStatus.Info, 482 } 483 if principalName, isSubordinate := unit.PrincipalName(); isSubordinate { 484 args.Principal = names.NewUnitTag(principalName) 485 } 486 if subs := unit.SubordinateNames(); len(subs) > 0 { 487 for _, subName := range subs { 488 args.Subordinates = append(args.Subordinates, names.NewUnitTag(subName)) 489 } 490 } 491 exUnit := exService.AddUnit(args) 492 // workload uses globalKey, agent uses globalAgentKey. 493 globalKey := unit.globalKey() 494 statusArgs, err := e.statusArgs(globalKey) 495 if err != nil { 496 return errors.Annotatef(err, "workload status for unit %s", unit.Name()) 497 } 498 exUnit.SetWorkloadStatus(statusArgs) 499 exUnit.SetWorkloadStatusHistory(e.statusHistoryArgs(globalKey)) 500 statusArgs, err = e.statusArgs(agentKey) 501 if err != nil { 502 return errors.Annotatef(err, "agent status for unit %s", unit.Name()) 503 } 504 exUnit.SetAgentStatus(statusArgs) 505 exUnit.SetAgentStatusHistory(e.statusHistoryArgs(agentKey)) 506 507 tools, err := unit.AgentTools() 508 if err != nil { 509 // This means the tools aren't set, but they should be. 510 return errors.Trace(err) 511 } 512 exUnit.SetTools(description.AgentToolsArgs{ 513 Version: tools.Version, 514 URL: tools.URL, 515 SHA256: tools.SHA256, 516 Size: tools.Size, 517 }) 518 exUnit.SetAnnotations(e.getAnnotations(globalKey)) 519 520 constraintsArgs, err := e.constraintsArgs(agentKey) 521 if err != nil { 522 return errors.Trace(err) 523 } 524 exUnit.SetConstraints(constraintsArgs) 525 } 526 527 return nil 528 } 529 530 func (e *exporter) relations() error { 531 rels, err := e.st.AllRelations() 532 if err != nil { 533 return errors.Trace(err) 534 } 535 e.logger.Debugf("read %d relations", len(rels)) 536 537 relationScopes, err := e.readAllRelationScopes() 538 if err != nil { 539 return errors.Trace(err) 540 } 541 542 for _, relation := range rels { 543 exRelation := e.model.AddRelation(description.RelationArgs{ 544 Id: relation.Id(), 545 Key: relation.String(), 546 }) 547 for _, ep := range relation.Endpoints() { 548 exEndPoint := exRelation.AddEndpoint(description.EndpointArgs{ 549 ServiceName: ep.ServiceName, 550 Name: ep.Name, 551 Role: string(ep.Role), 552 Interface: ep.Interface, 553 Optional: ep.Optional, 554 Limit: ep.Limit, 555 Scope: string(ep.Scope), 556 }) 557 // We expect a relationScope and settings for each of the 558 // units of the specified service. 559 units := e.units[ep.ServiceName] 560 for _, unit := range units { 561 ru, err := relation.Unit(unit) 562 if err != nil { 563 return errors.Trace(err) 564 } 565 key := ru.key() 566 if !relationScopes.Contains(key) { 567 return errors.Errorf("missing relation scope for %s and %s", relation, unit.Name()) 568 } 569 settingsDoc, found := e.settings[key] 570 if !found { 571 return errors.Errorf("missing relation settings for %s and %s", relation, unit.Name()) 572 } 573 exEndPoint.SetUnitSettings(unit.Name(), settingsDoc.Settings) 574 } 575 } 576 } 577 return nil 578 } 579 580 func (e *exporter) readAllRelationScopes() (set.Strings, error) { 581 relationScopes, closer := e.st.getCollection(relationScopesC) 582 defer closer() 583 584 docs := []relationScopeDoc{} 585 err := relationScopes.Find(nil).All(&docs) 586 if err != nil { 587 return nil, errors.Annotate(err, "cannot get all relation scopes") 588 } 589 e.logger.Debugf("found %d relationScope docs", len(docs)) 590 591 result := set.NewStrings() 592 for _, doc := range docs { 593 result.Add(doc.Key) 594 } 595 return result, nil 596 } 597 598 func (e *exporter) readAllUnits() (map[string][]*Unit, error) { 599 unitsCollection, closer := e.st.getCollection(unitsC) 600 defer closer() 601 602 docs := []unitDoc{} 603 err := unitsCollection.Find(nil).All(&docs) 604 if err != nil { 605 return nil, errors.Annotate(err, "cannot get all units") 606 } 607 e.logger.Debugf("found %d unit docs", len(docs)) 608 result := make(map[string][]*Unit) 609 for _, doc := range docs { 610 units := result[doc.Service] 611 result[doc.Service] = append(units, newUnit(e.st, &doc)) 612 } 613 return result, nil 614 } 615 616 func (e *exporter) readAllMeterStatus() (map[string]*meterStatusDoc, error) { 617 meterStatuses, closer := e.st.getCollection(meterStatusC) 618 defer closer() 619 620 docs := []meterStatusDoc{} 621 err := meterStatuses.Find(nil).All(&docs) 622 if err != nil { 623 return nil, errors.Annotate(err, "cannot get all meter status docs") 624 } 625 e.logger.Debugf("found %d meter status docs", len(docs)) 626 result := make(map[string]*meterStatusDoc) 627 for _, doc := range docs { 628 result[e.st.localID(doc.DocID)] = &doc 629 } 630 return result, nil 631 } 632 633 func (e *exporter) readLastConnectionTimes() (map[string]time.Time, error) { 634 lastConnections, closer := e.st.getCollection(modelUserLastConnectionC) 635 defer closer() 636 637 var docs []modelUserLastConnectionDoc 638 if err := lastConnections.Find(nil).All(&docs); err != nil { 639 return nil, errors.Trace(err) 640 } 641 642 result := make(map[string]time.Time) 643 for _, doc := range docs { 644 result[doc.UserName] = doc.LastConnection.UTC() 645 } 646 return result, nil 647 } 648 649 func (e *exporter) readAllAnnotations() error { 650 annotations, closer := e.st.getCollection(annotationsC) 651 defer closer() 652 653 var docs []annotatorDoc 654 if err := annotations.Find(nil).All(&docs); err != nil { 655 return errors.Trace(err) 656 } 657 e.logger.Debugf("read %d annotations docs", len(docs)) 658 659 e.annotations = make(map[string]annotatorDoc) 660 for _, doc := range docs { 661 e.annotations[doc.GlobalKey] = doc 662 } 663 return nil 664 } 665 666 func (e *exporter) readAllConstraints() error { 667 constraintsCollection, closer := e.st.getCollection(constraintsC) 668 defer closer() 669 670 // Since the constraintsDoc doesn't include any global key or _id 671 // fields, we can't just deserialize the entire collection into a slice 672 // of docs, so we get them all out with bson maps. 673 var docs []bson.M 674 err := constraintsCollection.Find(nil).All(&docs) 675 if err != nil { 676 return errors.Annotate(err, "failed to read constraints collection") 677 } 678 679 e.logger.Debugf("read %d constraints docs", len(docs)) 680 e.constraints = make(map[string]bson.M) 681 for _, doc := range docs { 682 docId, ok := doc["_id"].(string) 683 if !ok { 684 return errors.Errorf("expected string, got %s (%T)", doc["_id"], doc["_id"]) 685 } 686 id := e.st.localID(docId) 687 e.constraints[id] = doc 688 e.logger.Debugf("doc[%q] = %#v", id, doc) 689 } 690 return nil 691 } 692 693 // getAnnotations doesn't really care if there are any there or not 694 // for the key, but if they were there, they are removed so we can 695 // check at the end of the export for anything we have forgotten. 696 func (e *exporter) getAnnotations(key string) map[string]string { 697 result, found := e.annotations[key] 698 if found { 699 delete(e.annotations, key) 700 } 701 return result.Annotations 702 } 703 704 func (e *exporter) readAllSettings() error { 705 settings, closer := e.st.getCollection(settingsC) 706 defer closer() 707 708 var docs []settingsDoc 709 if err := settings.Find(nil).All(&docs); err != nil { 710 return errors.Trace(err) 711 } 712 713 e.settings = make(map[string]settingsDoc) 714 for _, doc := range docs { 715 key := e.st.localID(doc.DocID) 716 e.settings[key] = doc 717 } 718 return nil 719 } 720 721 func (e *exporter) readAllStatuses() error { 722 statuses, closer := e.st.getCollection(statusesC) 723 defer closer() 724 725 var docs []bson.M 726 err := statuses.Find(nil).All(&docs) 727 if err != nil { 728 return errors.Annotate(err, "failed to read status collection") 729 } 730 731 e.logger.Debugf("read %d status documents", len(docs)) 732 e.status = make(map[string]bson.M) 733 for _, doc := range docs { 734 docId, ok := doc["_id"].(string) 735 if !ok { 736 return errors.Errorf("expected string, got %s (%T)", doc["_id"], doc["_id"]) 737 } 738 id := e.st.localID(docId) 739 e.status[id] = doc 740 } 741 742 return nil 743 } 744 745 func (e *exporter) readAllStatusHistory() error { 746 statuses, closer := e.st.getCollection(statusesHistoryC) 747 defer closer() 748 749 count := 0 750 e.statusHistory = make(map[string][]historicalStatusDoc) 751 var doc historicalStatusDoc 752 iter := statuses.Find(nil).Sort("-updated").Iter() 753 defer iter.Close() 754 for iter.Next(&doc) { 755 history := e.statusHistory[doc.GlobalKey] 756 e.statusHistory[doc.GlobalKey] = append(history, doc) 757 count++ 758 } 759 760 if err := iter.Err(); err != nil { 761 return errors.Annotate(err, "failed to read status history collection") 762 } 763 764 e.logger.Debugf("read %d status history documents", count) 765 766 return nil 767 } 768 769 func (e *exporter) statusArgs(globalKey string) (description.StatusArgs, error) { 770 result := description.StatusArgs{} 771 statusDoc, found := e.status[globalKey] 772 if !found { 773 return result, errors.NotFoundf("status data for %s", globalKey) 774 } 775 776 status, ok := statusDoc["status"].(string) 777 if !ok { 778 return result, errors.Errorf("expected string for status, got %T", statusDoc["status"]) 779 } 780 info, ok := statusDoc["statusinfo"].(string) 781 if !ok { 782 return result, errors.Errorf("expected string for statusinfo, got %T", statusDoc["statusinfo"]) 783 } 784 // data is an embedded map and comes out as a bson.M 785 // A bson.M is map[string]interface{}, so we can type cast it. 786 data, ok := statusDoc["statusdata"].(bson.M) 787 if !ok { 788 return result, errors.Errorf("expected map for data, got %T", statusDoc["statusdata"]) 789 } 790 dataMap := map[string]interface{}(data) 791 updated, ok := statusDoc["updated"].(int64) 792 if !ok { 793 return result, errors.Errorf("expected int64 for updated, got %T", statusDoc["updated"]) 794 } 795 796 result.Value = status 797 result.Message = info 798 result.Data = dataMap 799 result.Updated = time.Unix(0, updated) 800 return result, nil 801 } 802 803 func (e *exporter) statusHistoryArgs(globalKey string) []description.StatusArgs { 804 history := e.statusHistory[globalKey] 805 result := make([]description.StatusArgs, len(history)) 806 e.logger.Debugf("found %d status history docs for %s", len(history), globalKey) 807 for i, doc := range history { 808 result[i] = description.StatusArgs{ 809 Value: string(doc.Status), 810 Message: doc.StatusInfo, 811 Data: doc.StatusData, 812 Updated: time.Unix(0, doc.Updated), 813 } 814 } 815 816 return result 817 } 818 819 func (e *exporter) constraintsArgs(globalKey string) (description.ConstraintsArgs, error) { 820 doc, found := e.constraints[globalKey] 821 if !found { 822 // No constraints for this key. 823 e.logger.Debugf("no constraints found for key %q", globalKey) 824 return description.ConstraintsArgs{}, nil 825 } 826 // We capture any type error using a closure to avoid having to return 827 // multiple values from the optional functions. This does mean that we will 828 // only report on the last one, but that is fine as there shouldn't be any. 829 var optionalErr error 830 optionalString := func(name string) string { 831 switch value := doc[name].(type) { 832 case nil: 833 case string: 834 return value 835 default: 836 optionalErr = errors.Errorf("expected uint64 for %s, got %T", name, value) 837 } 838 return "" 839 } 840 optionalInt := func(name string) uint64 { 841 switch value := doc[name].(type) { 842 case nil: 843 case uint64: 844 return value 845 case int64: 846 return uint64(value) 847 default: 848 optionalErr = errors.Errorf("expected uint64 for %s, got %T", name, value) 849 } 850 return 0 851 } 852 optionalStringSlice := func(name string) []string { 853 switch value := doc[name].(type) { 854 case nil: 855 case []string: 856 return value 857 default: 858 optionalErr = errors.Errorf("expected []string] for %s, got %T", name, value) 859 } 860 return nil 861 } 862 result := description.ConstraintsArgs{ 863 Architecture: optionalString("arch"), 864 Container: optionalString("container"), 865 CpuCores: optionalInt("cpucores"), 866 CpuPower: optionalInt("cpupower"), 867 InstanceType: optionalString("instancetype"), 868 Memory: optionalInt("mem"), 869 RootDisk: optionalInt("rootdisk"), 870 Spaces: optionalStringSlice("spaces"), 871 Tags: optionalStringSlice("tags"), 872 } 873 if optionalErr != nil { 874 return description.ConstraintsArgs{}, errors.Trace(optionalErr) 875 } 876 return result, nil 877 } 878 879 func (e *exporter) readAllSettingsRefCounts() (map[string]int, error) { 880 refCounts, closer := e.st.getCollection(settingsrefsC) 881 defer closer() 882 883 var docs []bson.M 884 err := refCounts.Find(nil).All(&docs) 885 if err != nil { 886 return nil, errors.Annotate(err, "failed to read settings refcount collection") 887 } 888 889 e.logger.Debugf("read %d settings refcount documents", len(docs)) 890 result := make(map[string]int) 891 for _, doc := range docs { 892 docId, ok := doc["_id"].(string) 893 if !ok { 894 return nil, errors.Errorf("expected string, got %s (%T)", doc["_id"], doc["_id"]) 895 } 896 id := e.st.localID(docId) 897 count, ok := doc["refcount"].(int) 898 if !ok { 899 return nil, errors.Errorf("expected int, got %s (%T)", doc["refcount"], doc["refcount"]) 900 } 901 result[id] = count 902 } 903 904 return result, nil 905 } 906 907 func (e *exporter) logExtras() { 908 // As annotations are saved into the model, they are removed from the 909 // exporter's map. If there are any left at the end, we are missing 910 // things. Not an error just now, just a warning that we have missed 911 // something. Could potentially be an error at a later date when 912 // migrations are complete (but probably not). 913 for key, doc := range e.annotations { 914 e.logger.Warningf("unexported annotation for %s, %s", doc.Tag, key) 915 } 916 }