github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/migration_internal_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "github.com/juju/utils/set" 8 gc "gopkg.in/check.v1" 9 "gopkg.in/juju/charm.v6-unstable" 10 11 "github.com/juju/juju/testing" 12 ) 13 14 type MigrationSuite struct{} 15 16 var _ = gc.Suite(&MigrationSuite{}) 17 18 func (s *MigrationSuite) TestKnownCollections(c *gc.C) { 19 completedCollections := set.NewStrings( 20 annotationsC, 21 blocksC, 22 cloudimagemetadataC, 23 constraintsC, 24 modelsC, 25 modelUsersC, 26 modelUserLastConnectionC, 27 permissionsC, 28 settingsC, 29 sequenceC, 30 sshHostKeysC, 31 statusesC, 32 statusesHistoryC, 33 34 // machine 35 instanceDataC, 36 machinesC, 37 openedPortsC, 38 39 // application / unit 40 leasesC, 41 applicationsC, 42 unitsC, 43 meterStatusC, // red / green status for metrics of units 44 payloadsC, 45 46 // relation 47 relationsC, 48 relationScopesC, 49 50 // networking 51 ipAddressesC, 52 spacesC, 53 linkLayerDevicesC, 54 subnetsC, 55 56 // storage 57 blockDevicesC, 58 59 // cloudimagemetadata 60 cloudimagemetadataC, 61 62 // actions 63 actionsC, 64 65 // storage 66 filesystemsC, 67 filesystemAttachmentsC, 68 storageAttachmentsC, 69 storageConstraintsC, 70 storageInstancesC, 71 volumesC, 72 volumeAttachmentsC, 73 ) 74 75 ignoredCollections := set.NewStrings( 76 // Precheck ensures that there are no cleanup docs or pending 77 // machine removals. 78 cleanupsC, 79 machineRemovalsC, 80 // The autocert cache is non-critical. After migration 81 // you'll just need to acquire new certificates. 82 autocertCacheC, 83 // We don't export the controller model at this stage. 84 controllersC, 85 // Clouds aren't migrated. They must exist in the 86 // target controller already. 87 cloudsC, 88 // Cloud credentials aren't migrated. They must exist in the 89 // target controller already. 90 cloudCredentialsC, 91 // This is controller global, and related to the system state of the 92 // embedded GUI. 93 guimetadataC, 94 // This is controller global, not migrated. 95 guisettingsC, 96 // Users aren't migrated. 97 usersC, 98 userLastLoginC, 99 // Controller users contain extra data about users therefore 100 // are not migrated either. 101 controllerUsersC, 102 // userenvnameC is just to provide a unique key constraint. 103 usermodelnameC, 104 // Metrics aren't migrated. 105 metricsC, 106 // Backup and restore information is not migrated. 107 restoreInfoC, 108 // reference counts are implementation details that should be 109 // reconstructed on the other side. 110 refcountsC, 111 // upgradeInfoC is used to coordinate upgrades and schema migrations, 112 // and aren't needed for model migrations. 113 upgradeInfoC, 114 // Not exported, but the tools will possibly need to be either bundled 115 // with the representation or sent separately. 116 toolsmetadataC, 117 // Bakery storage items are non-critical. We store root keys for 118 // temporary credentials in there; after migration you'll just have 119 // to log back in. 120 bakeryStorageItemsC, 121 // Transaction stuff. 122 "txns", 123 "txns.log", 124 125 // We don't import any of the migration collections. 126 migrationsC, 127 migrationsStatusC, 128 migrationsActiveC, 129 migrationsMinionSyncC, 130 131 // The container ref document is primarily there to keep track 132 // of a particular machine's containers. The migration format 133 // uses object containment for this purpose. 134 containerRefsC, 135 // The min units collection is only used to trigger a watcher 136 // in order to have the service add or remove units if the minimum 137 // number of units is changed. The Service doc has all we need 138 // for migratino. 139 minUnitsC, 140 // This is a transitory collection of units that need to be assigned 141 // to machines. 142 assignUnitC, 143 144 // The model entity references collection will be repopulated 145 // after importing the model. It does not need to be migrated 146 // separately. 147 modelEntityRefsC, 148 149 // This is marked as deprecated, and should probably be removed. 150 actionresultsC, 151 152 // These are recreated whilst migrating other network entities. 153 providerIDsC, 154 linkLayerDevicesRefsC, 155 156 // Recreated whilst migrating actions. 157 actionNotificationsC, 158 ) 159 160 // THIS SET WILL BE REMOVED WHEN MIGRATIONS ARE COMPLETE 161 todoCollections := set.NewStrings( 162 // model configuration 163 globalSettingsC, 164 165 // machine 166 rebootC, 167 168 // service / unit 169 charmsC, 170 "resources", 171 endpointBindingsC, 172 173 // uncategorised 174 metricsManagerC, // should really be copied across 175 auditingC, 176 ) 177 178 envCollections := set.NewStrings() 179 for name := range allCollections() { 180 envCollections.Add(name) 181 } 182 183 known := completedCollections.Union(ignoredCollections) 184 185 remainder := envCollections.Difference(known) 186 remainder = remainder.Difference(todoCollections) 187 188 // If this test fails, it means that a new collection has been added 189 // but migrations for it has not been done. This is a Bad Thing™. 190 // Beware, if your collection is something controller-related it might 191 // not need migration (such as Users or ControllerUsers) in that 192 // case they only need to be accounted for among the ignored collections. 193 c.Assert(remainder, gc.HasLen, 0) 194 } 195 196 func (s *MigrationSuite) TestModelDocFields(c *gc.C) { 197 fields := set.NewStrings( 198 // UUID and Name are constructed from the model config. 199 "UUID", 200 "Name", 201 // Life will always be alive, or we won't be migrating. 202 "Life", 203 // ControllerUUID is recreated when the new model 204 // is created in the new controller (yay name changes). 205 "ControllerUUID", 206 207 "MigrationMode", 208 "Owner", 209 "Cloud", 210 "CloudRegion", 211 "CloudCredential", 212 "LatestAvailableTools", 213 ) 214 s.AssertExportedFields(c, modelDoc{}, fields) 215 } 216 217 func (s *MigrationSuite) TestUserAccessDocFields(c *gc.C) { 218 fields := set.NewStrings( 219 // ID is the same as UserName (but lowercased) 220 "ID", 221 // ObjectUUID shouldn't be exported, and is inherited 222 // from the model definition. 223 "ObjectUUID", 224 // Tracked fields: 225 "UserName", 226 "DisplayName", 227 "CreatedBy", 228 "DateCreated", 229 ) 230 s.AssertExportedFields(c, userAccessDoc{}, fields) 231 } 232 233 func (s *MigrationSuite) TestPermissionDocFields(c *gc.C) { 234 fields := set.NewStrings( 235 "ID", 236 "ObjectGlobalKey", 237 "SubjectGlobalKey", 238 "Access", 239 ) 240 s.AssertExportedFields(c, permissionDoc{}, fields) 241 } 242 243 func (s *MigrationSuite) TestEnvUserLastConnectionDocFields(c *gc.C) { 244 fields := set.NewStrings( 245 // ID is the same as UserName (but lowercased) 246 "ID", 247 // ModelUUID shouldn't be exported, and is inherited 248 // from the model definition. 249 "ModelUUID", 250 // UserName is captured in the migration.User. 251 "UserName", 252 "LastConnection", 253 ) 254 s.AssertExportedFields(c, modelUserLastConnectionDoc{}, fields) 255 } 256 257 func (s *MigrationSuite) TestMachineDocFields(c *gc.C) { 258 ignored := set.NewStrings( 259 // DocID is the env + machine id 260 "DocID", 261 // ID is the machine id 262 "Id", 263 // ModelUUID shouldn't be exported, and is inherited 264 // from the model definition. 265 "ModelUUID", 266 // Life is always alive, confirmed by export precheck. 267 "Life", 268 // NoVote and HasVote only matter for machines with manage state job 269 // and we don't support migrating the controller model. 270 "NoVote", 271 "HasVote", 272 // Ignored at this stage, could be an issue if mongo 3.0 isn't 273 // available. 274 "StopMongoUntilVersion", 275 ) 276 migrated := set.NewStrings( 277 "Addresses", 278 "ContainerType", 279 "Jobs", 280 "MachineAddresses", 281 "Nonce", 282 "PasswordHash", 283 "Clean", 284 "Volumes", 285 "Filesystems", 286 "Placement", 287 "PreferredPrivateAddress", 288 "PreferredPublicAddress", 289 "Principals", 290 "Series", 291 "SupportedContainers", 292 "SupportedContainersKnown", 293 "Tools", 294 ) 295 s.AssertExportedFields(c, machineDoc{}, migrated.Union(ignored)) 296 } 297 298 func (s *MigrationSuite) TestInstanceDataFields(c *gc.C) { 299 fields := set.NewStrings( 300 // DocID is the env + machine id 301 "DocID", 302 "MachineId", 303 // ModelUUID shouldn't be exported, and is inherited 304 // from the model definition. 305 "ModelUUID", 306 307 "InstanceId", 308 "Status", 309 "Arch", 310 "Mem", 311 "RootDisk", 312 "CpuCores", 313 "CpuPower", 314 "Tags", 315 "AvailZone", 316 ) 317 s.AssertExportedFields(c, instanceData{}, fields) 318 } 319 320 func (s *MigrationSuite) TestApplicationDocFields(c *gc.C) { 321 ignored := set.NewStrings( 322 // DocID is the env + name 323 "DocID", 324 // ModelUUID shouldn't be exported, and is inherited 325 // from the model definition. 326 "ModelUUID", 327 // Always alive, not explicitly exported. 328 "Life", 329 // TxnRevno is mgo internals and should not be migrated. 330 "TxnRevno", 331 // UnitCount is handled by the number of units for the exported service. 332 "UnitCount", 333 // RelationCount is handled by the number of times the application name 334 // appears in relation endpoints. 335 "RelationCount", 336 ) 337 migrated := set.NewStrings( 338 "Name", 339 "Series", 340 "Subordinate", 341 "CharmURL", 342 "Channel", 343 "CharmModifiedVersion", 344 "ForceCharm", 345 "Exposed", 346 "MinUnits", 347 "MetricCredentials", 348 ) 349 s.AssertExportedFields(c, applicationDoc{}, migrated.Union(ignored)) 350 } 351 352 func (s *MigrationSuite) TestUnitDocFields(c *gc.C) { 353 ignored := set.NewStrings( 354 "ModelUUID", 355 "DocID", 356 "Life", 357 // Application is implicit in the migration structure through containment. 358 "Application", 359 // Resolved is not migrated as we check that all is good before we start. 360 "Resolved", 361 // Series and CharmURL also come from the service. 362 "Series", 363 "CharmURL", 364 "TxnRevno", 365 ) 366 migrated := set.NewStrings( 367 "Name", 368 "Principal", 369 "Subordinates", 370 "StorageAttachmentCount", 371 "MachineId", 372 "Tools", 373 "PasswordHash", 374 ) 375 s.AssertExportedFields(c, unitDoc{}, migrated.Union(ignored)) 376 } 377 378 func (s *MigrationSuite) TestPortsDocFields(c *gc.C) { 379 fields := set.NewStrings( 380 // DocID itself isn't migrated 381 "DocID", 382 // ModelUUID shouldn't be exported, and is inherited 383 // from the model definition. 384 "ModelUUID", 385 // MachineID is implicit in the migration structure through containment. 386 "MachineID", 387 "SubnetID", 388 "Ports", 389 // TxnRevno isn't migrated. 390 "TxnRevno", 391 ) 392 s.AssertExportedFields(c, portsDoc{}, fields) 393 } 394 395 func (s *MigrationSuite) TestMeterStatusDocFields(c *gc.C) { 396 fields := set.NewStrings( 397 // DocID itself isn't migrated 398 "DocID", 399 // ModelUUID shouldn't be exported, and is inherited 400 // from the model definition. 401 "ModelUUID", 402 "Code", 403 "Info", 404 ) 405 s.AssertExportedFields(c, meterStatusDoc{}, fields) 406 } 407 408 func (s *MigrationSuite) TestRelationDocFields(c *gc.C) { 409 fields := set.NewStrings( 410 // DocID itself isn't migrated 411 "DocID", 412 // ModelUUID shouldn't be exported, and is inherited 413 // from the model definition. 414 "ModelUUID", 415 "Key", 416 "Id", 417 "Endpoints", 418 // Life isn't exported, only alive. 419 "Life", 420 // UnitCount isn't explicitly exported, but defined by the stored 421 // unit settings data for the relation endpoint. 422 "UnitCount", 423 ) 424 s.AssertExportedFields(c, relationDoc{}, fields) 425 // We also need to check the Endpoint and nested charm.Relation field. 426 endpointFields := set.NewStrings("ApplicationName", "Relation") 427 s.AssertExportedFields(c, Endpoint{}, endpointFields) 428 charmRelationFields := set.NewStrings( 429 "Name", 430 "Role", 431 "Interface", 432 "Optional", 433 "Limit", 434 "Scope", 435 ) 436 s.AssertExportedFields(c, charm.Relation{}, charmRelationFields) 437 } 438 439 func (s *MigrationSuite) TestRelationScopeDocFields(c *gc.C) { 440 fields := set.NewStrings( 441 // DocID itself isn't migrated 442 "DocID", 443 // ModelUUID shouldn't be exported, and is inherited 444 // from the model definition. 445 "ModelUUID", 446 "Key", 447 // Departing isn't exported as we only deal with live, stable systems. 448 "Departing", 449 ) 450 s.AssertExportedFields(c, relationScopeDoc{}, fields) 451 } 452 453 func (s *MigrationSuite) TestAnnatatorDocFields(c *gc.C) { 454 fields := set.NewStrings( 455 // ModelUUID shouldn't be exported, and is inherited 456 // from the model definition. 457 "ModelUUID", 458 "GlobalKey", 459 "Tag", 460 "Annotations", 461 ) 462 s.AssertExportedFields(c, annotatorDoc{}, fields) 463 } 464 465 func (s *MigrationSuite) TestBlockDocFields(c *gc.C) { 466 ignored := set.NewStrings( 467 // The doc id is a sequence value that has no meaning. 468 // It really doesn't need to be a sequence. 469 "DocID", 470 // ModelUUID shouldn't be exported, and is inherited 471 // from the model definition. 472 "ModelUUID", 473 // Tag is just string representation of the model tag, 474 // which also contains the model-uuid. 475 "Tag", 476 ) 477 migrated := set.NewStrings( 478 "Type", 479 "Message", 480 ) 481 fields := migrated.Union(ignored) 482 s.AssertExportedFields(c, blockDoc{}, fields) 483 } 484 485 func (s *MigrationSuite) TestSequenceDocFields(c *gc.C) { 486 fields := set.NewStrings( 487 // ModelUUID shouldn't be exported, and is inherited 488 // from the model definition. 489 "ModelUUID", 490 "DocID", 491 "Name", 492 "Counter", 493 ) 494 s.AssertExportedFields(c, sequenceDoc{}, fields) 495 } 496 497 func (s *MigrationSuite) TestConstraintsDocFields(c *gc.C) { 498 fields := set.NewStrings( 499 // ModelUUID shouldn't be exported, and is inherited 500 // from the model definition. 501 "ModelUUID", 502 "Arch", 503 "CpuCores", 504 "CpuPower", 505 "Mem", 506 "RootDisk", 507 "InstanceType", 508 "Container", 509 "Tags", 510 "Spaces", 511 "VirtType", 512 ) 513 s.AssertExportedFields(c, constraintsDoc{}, fields) 514 } 515 516 func (s *MigrationSuite) TestHistoricalStatusDocFields(c *gc.C) { 517 fields := set.NewStrings( 518 // ModelUUID shouldn't be exported, and is inherited 519 // from the model definition. 520 "ModelUUID", 521 "GlobalKey", 522 "Status", 523 "StatusInfo", 524 "StatusData", 525 "Updated", 526 ) 527 s.AssertExportedFields(c, historicalStatusDoc{}, fields) 528 } 529 530 func (s *MigrationSuite) TestSpaceDocFields(c *gc.C) { 531 ignored := set.NewStrings( 532 // Always alive, not explicitly exported. 533 "Life", 534 ) 535 migrated := set.NewStrings( 536 "Name", 537 "IsPublic", 538 "ProviderId", 539 ) 540 s.AssertExportedFields(c, spaceDoc{}, migrated.Union(ignored)) 541 } 542 543 func (s *MigrationSuite) TestBlockDeviceFields(c *gc.C) { 544 ignored := set.NewStrings( 545 "DocID", 546 "ModelUUID", 547 // We manage machine through containment. 548 "Machine", 549 ) 550 migrated := set.NewStrings( 551 "BlockDevices", 552 ) 553 s.AssertExportedFields(c, blockDevicesDoc{}, migrated.Union(ignored)) 554 // The meat is in the type stored in "BlockDevices". 555 migrated = set.NewStrings( 556 "DeviceName", 557 "DeviceLinks", 558 "Label", 559 "UUID", 560 "HardwareId", 561 "BusAddress", 562 "Size", 563 "FilesystemType", 564 "InUse", 565 "MountPoint", 566 ) 567 s.AssertExportedFields(c, BlockDeviceInfo{}, migrated) 568 } 569 570 func (s *MigrationSuite) TestSubnetDocFields(c *gc.C) { 571 ignored := set.NewStrings( 572 // DocID is the env + name 573 "DocID", 574 // ModelUUID shouldn't be exported, and is inherited 575 // from the model definition. 576 "ModelUUID", 577 // Always alive, not explicitly exported. 578 "Life", 579 580 // Currently unused (never set or exposed). 581 "IsPublic", 582 ) 583 migrated := set.NewStrings( 584 "CIDR", 585 "VLANTag", 586 "SpaceName", 587 "ProviderId", 588 "AvailabilityZone", 589 ) 590 s.AssertExportedFields(c, subnetDoc{}, migrated.Union(ignored)) 591 } 592 593 func (s *MigrationSuite) TestIPAddressDocFields(c *gc.C) { 594 ignored := set.NewStrings( 595 "DocID", 596 "ModelUUID", 597 ) 598 migrated := set.NewStrings( 599 "DeviceName", 600 "MachineID", 601 "DNSSearchDomains", 602 "GatewayAddress", 603 "ProviderID", 604 "DNSServers", 605 "SubnetCIDR", 606 "ConfigMethod", 607 "Value", 608 ) 609 s.AssertExportedFields(c, ipAddressDoc{}, migrated.Union(ignored)) 610 } 611 612 func (s *MigrationSuite) TestLinkLayerDeviceDocFields(c *gc.C) { 613 ignored := set.NewStrings( 614 "ModelUUID", 615 "DocID", 616 ) 617 migrated := set.NewStrings( 618 "MachineID", 619 "ProviderID", 620 "Name", 621 "MTU", 622 "Type", 623 "MACAddress", 624 "IsAutoStart", 625 "IsUp", 626 "ParentName", 627 ) 628 s.AssertExportedFields(c, linkLayerDeviceDoc{}, migrated.Union(ignored)) 629 } 630 631 func (s *MigrationSuite) TestSSHHostKeyDocFields(c *gc.C) { 632 ignored := set.NewStrings() 633 migrated := set.NewStrings( 634 "Keys", 635 ) 636 s.AssertExportedFields(c, sshHostKeysDoc{}, migrated.Union(ignored)) 637 } 638 639 func (s *MigrationSuite) TestActionDocFields(c *gc.C) { 640 ignored := set.NewStrings( 641 "ModelUUID", 642 ) 643 migrated := set.NewStrings( 644 "DocId", 645 "Receiver", 646 "Name", 647 "Enqueued", 648 "Started", 649 "Completed", 650 "Parameters", 651 "Results", 652 "Message", 653 "Status", 654 ) 655 s.AssertExportedFields(c, actionDoc{}, migrated.Union(ignored)) 656 } 657 658 func (s *MigrationSuite) TestVolumeDocFields(c *gc.C) { 659 ignored := set.NewStrings( 660 "ModelUUID", 661 "DocID", 662 "Life", 663 ) 664 migrated := set.NewStrings( 665 "Name", 666 "StorageId", 667 "AttachmentCount", // through count of attachment instances 668 "Binding", 669 "Info", 670 "Params", 671 ) 672 s.AssertExportedFields(c, volumeDoc{}, migrated.Union(ignored)) 673 // The info and params fields ar structs. 674 s.AssertExportedFields(c, VolumeInfo{}, set.NewStrings( 675 "HardwareId", "Size", "Pool", "VolumeId", "Persistent")) 676 s.AssertExportedFields(c, VolumeParams{}, set.NewStrings( 677 "Size", "Pool")) 678 } 679 680 func (s *MigrationSuite) TestVolumeAttachmentDocFields(c *gc.C) { 681 ignored := set.NewStrings( 682 "ModelUUID", 683 "DocID", 684 "Life", 685 ) 686 migrated := set.NewStrings( 687 "Volume", 688 "Machine", 689 "Info", 690 "Params", 691 ) 692 s.AssertExportedFields(c, volumeAttachmentDoc{}, migrated.Union(ignored)) 693 // The info and params fields ar structs. 694 s.AssertExportedFields(c, VolumeAttachmentInfo{}, set.NewStrings( 695 "DeviceName", "DeviceLink", "BusAddress", "ReadOnly")) 696 s.AssertExportedFields(c, VolumeAttachmentParams{}, set.NewStrings( 697 "ReadOnly")) 698 } 699 700 func (s *MigrationSuite) TestFilesystemDocFields(c *gc.C) { 701 ignored := set.NewStrings( 702 "ModelUUID", 703 "DocID", 704 "Life", 705 ) 706 migrated := set.NewStrings( 707 "FilesystemId", 708 "StorageId", 709 "VolumeId", 710 "AttachmentCount", // through count of attachment instances 711 "Binding", 712 "Info", 713 "Params", 714 ) 715 s.AssertExportedFields(c, filesystemDoc{}, migrated.Union(ignored)) 716 // The info and params fields ar structs. 717 s.AssertExportedFields(c, FilesystemInfo{}, set.NewStrings( 718 "Size", "Pool", "FilesystemId")) 719 s.AssertExportedFields(c, FilesystemParams{}, set.NewStrings( 720 "Size", "Pool")) 721 } 722 723 func (s *MigrationSuite) TestFilesystemAttachmentDocFields(c *gc.C) { 724 ignored := set.NewStrings( 725 "ModelUUID", 726 "DocID", 727 "Life", 728 ) 729 migrated := set.NewStrings( 730 "Filesystem", 731 "Machine", 732 "Info", 733 "Params", 734 ) 735 s.AssertExportedFields(c, filesystemAttachmentDoc{}, migrated.Union(ignored)) 736 // The info and params fields ar structs. 737 s.AssertExportedFields(c, FilesystemAttachmentInfo{}, set.NewStrings( 738 "MountPoint", "ReadOnly")) 739 s.AssertExportedFields(c, FilesystemAttachmentParams{}, set.NewStrings( 740 "Location", "ReadOnly")) 741 } 742 743 func (s *MigrationSuite) TestStorageInstanceDocFields(c *gc.C) { 744 ignored := set.NewStrings( 745 "ModelUUID", 746 "DocID", 747 "Life", 748 ) 749 migrated := set.NewStrings( 750 "Id", 751 "Kind", 752 "Owner", 753 "StorageName", 754 "AttachmentCount", // through count of attachment instances 755 ) 756 s.AssertExportedFields(c, storageInstanceDoc{}, migrated.Union(ignored)) 757 } 758 759 func (s *MigrationSuite) TestStorageAttachmentDocFields(c *gc.C) { 760 ignored := set.NewStrings( 761 "ModelUUID", 762 "DocID", 763 "Life", 764 ) 765 migrated := set.NewStrings( 766 "Unit", 767 "StorageInstance", 768 ) 769 s.AssertExportedFields(c, storageAttachmentDoc{}, migrated.Union(ignored)) 770 } 771 772 func (s *MigrationSuite) TestStorageConstraintsDocFields(c *gc.C) { 773 ignored := set.NewStrings( 774 "ModelUUID", 775 "DocID", 776 ) 777 migrated := set.NewStrings( 778 "Constraints", 779 ) 780 s.AssertExportedFields(c, storageConstraintsDoc{}, migrated.Union(ignored)) 781 } 782 783 func (s *MigrationSuite) TestPayloadDocFields(c *gc.C) { 784 definedThroughContainment := set.NewStrings( 785 "UnitID", 786 "MachineID", 787 ) 788 migrated := set.NewStrings( 789 "Name", 790 "Type", 791 "RawID", 792 "State", 793 "Labels", 794 ) 795 s.AssertExportedFields(c, payloadDoc{}, migrated.Union(definedThroughContainment)) 796 } 797 798 func (s *MigrationSuite) AssertExportedFields(c *gc.C, doc interface{}, fields set.Strings) { 799 expected := testing.GetExportedFields(doc) 800 unknown := expected.Difference(fields) 801 removed := fields.Difference(expected) 802 // If this test fails, it means that extra fields have been added to the 803 // doc without thinking about the migration implications. 804 c.Check(unknown, gc.HasLen, 0) 805 c.Assert(removed, gc.HasLen, 0) 806 }