github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/migration/migration_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migration_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "io/ioutil" 11 12 "github.com/juju/errors" 13 "github.com/juju/names" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 "github.com/juju/version" 17 gc "gopkg.in/check.v1" 18 "gopkg.in/juju/charm.v6-unstable" 19 "gopkg.in/mgo.v2" 20 21 "github.com/juju/juju/api" 22 "github.com/juju/juju/cmd/modelcmd" 23 "github.com/juju/juju/core/description" 24 "github.com/juju/juju/environs" 25 "github.com/juju/juju/jujuclient/jujuclienttesting" 26 "github.com/juju/juju/migration" 27 "github.com/juju/juju/provider/dummy" 28 _ "github.com/juju/juju/provider/dummy" 29 "github.com/juju/juju/state" 30 "github.com/juju/juju/state/binarystorage" 31 "github.com/juju/juju/state/storage" 32 statetesting "github.com/juju/juju/state/testing" 33 "github.com/juju/juju/testing" 34 "github.com/juju/juju/tools" 35 ) 36 37 type ImportSuite struct { 38 statetesting.StateSuite 39 } 40 41 var _ = gc.Suite(&ImportSuite{}) 42 43 func (s *ImportSuite) SetUpTest(c *gc.C) { 44 // Specify the config to use for the controller model before calling 45 // SetUpTest of the StateSuite, otherwise we get testing.ModelConfig(c). 46 // The default provider type specified in the testing.ModelConfig function 47 // is one that isn't registered as a valid provider. For our tests here we 48 // need a real registered provider, so we use the dummy provider. 49 // NOTE: make a better test provider. 50 env, err := environs.Prepare( 51 modelcmd.BootstrapContext(testing.Context(c)), 52 jujuclienttesting.NewMemStore(), 53 environs.PrepareParams{ 54 ControllerName: "dummycontroller", 55 BaseConfig: dummy.SampleConfig(), 56 CloudName: "dummy", 57 }, 58 ) 59 c.Assert(err, jc.ErrorIsNil) 60 61 s.InitialConfig = testing.CustomModelConfig(c, env.Config().AllAttrs()) 62 s.StateSuite.SetUpTest(c) 63 } 64 65 func (s *ImportSuite) TestBadBytes(c *gc.C) { 66 bytes := []byte("not a model") 67 model, st, err := migration.ImportModel(s.State, bytes) 68 c.Check(st, gc.IsNil) 69 c.Check(model, gc.IsNil) 70 c.Assert(err, gc.ErrorMatches, "yaml: unmarshal errors:\n.*") 71 } 72 73 func (s *ImportSuite) TestImportModel(c *gc.C) { 74 model, err := s.State.Export() 75 c.Check(err, jc.ErrorIsNil) 76 77 controllerConfig, err := s.State.ModelConfig() 78 c.Check(err, jc.ErrorIsNil) 79 80 // Update the config values in the exported model for different values for 81 // "state-port", "api-port", and "ca-cert". Also give the model a new UUID 82 // and name so we can import it nicely. 83 model.UpdateConfig(map[string]interface{}{ 84 "name": "new-model", 85 "uuid": utils.MustNewUUID().String(), 86 "state-port": 12345, 87 "api-port": 54321, 88 "ca-cert": "not really a cert", 89 }) 90 91 bytes, err := description.Serialize(model) 92 c.Check(err, jc.ErrorIsNil) 93 94 dbModel, dbState, err := migration.ImportModel(s.State, bytes) 95 c.Check(err, jc.ErrorIsNil) 96 defer dbState.Close() 97 98 dbConfig, err := dbModel.Config() 99 c.Assert(err, jc.ErrorIsNil) 100 attrs := dbConfig.AllAttrs() 101 c.Assert(attrs["state-port"], gc.Equals, controllerConfig.StatePort()) 102 c.Assert(attrs["api-port"], gc.Equals, controllerConfig.APIPort()) 103 cacert, ok := controllerConfig.CACert() 104 c.Assert(ok, jc.IsTrue) 105 c.Assert(attrs["ca-cert"], gc.Equals, cacert) 106 c.Assert(attrs["controller-uuid"], gc.Equals, controllerConfig.UUID()) 107 } 108 109 func (s *ImportSuite) TestUploadBinariesTools(c *gc.C) { 110 // Create a model that has three different tools versions: 111 // one for a machine, one for a container, and one for a unit agent. 112 // We don't care about the actual validity of the model (it isn't). 113 model := description.NewModel(description.ModelArgs{ 114 Owner: names.NewUserTag("me"), 115 }) 116 machine := model.AddMachine(description.MachineArgs{ 117 Id: names.NewMachineTag("0"), 118 }) 119 machine.SetTools(description.AgentToolsArgs{ 120 Version: version.MustParseBinary("2.0.1-trusty-amd64"), 121 }) 122 container := machine.AddContainer(description.MachineArgs{ 123 Id: names.NewMachineTag("0/lxc/0"), 124 }) 125 container.SetTools(description.AgentToolsArgs{ 126 Version: version.MustParseBinary("2.0.5-trusty-amd64"), 127 }) 128 service := model.AddService(description.ServiceArgs{ 129 Tag: names.NewServiceTag("magic"), 130 CharmURL: "local:trusty/magic", 131 }) 132 unit := service.AddUnit(description.UnitArgs{ 133 Tag: names.NewUnitTag("magic/0"), 134 }) 135 unit.SetTools(description.AgentToolsArgs{ 136 Version: version.MustParseBinary("2.0.3-trusty-amd64"), 137 }) 138 139 uploader := &fakeUploader{tools: make(map[version.Binary]string)} 140 config := migration.UploadBinariesConfig{ 141 State: &fakeStateStorage{}, 142 Model: model, 143 Target: &fakeAPIConnection{}, 144 GetCharmUploader: func(api.Connection) migration.CharmUploader { return &noOpUploader{} }, 145 GetToolsUploader: func(target api.Connection) migration.ToolsUploader { 146 return uploader 147 }, 148 GetStateStorage: func(migration.UploadBackend) storage.Storage { return &fakeCharmsStorage{} }, 149 GetCharmStoragePath: func(migration.UploadBackend, *charm.URL) (string, error) { return "", nil }, 150 } 151 err := migration.UploadBinaries(config) 152 c.Assert(err, jc.ErrorIsNil) 153 154 c.Assert(uploader.tools, jc.DeepEquals, map[version.Binary]string{ 155 version.MustParseBinary("2.0.1-trusty-amd64"): "fake tools 2.0.1-trusty-amd64", 156 version.MustParseBinary("2.0.3-trusty-amd64"): "fake tools 2.0.3-trusty-amd64", 157 version.MustParseBinary("2.0.5-trusty-amd64"): "fake tools 2.0.5-trusty-amd64", 158 }) 159 } 160 161 func (s *ImportSuite) TestStreamCharmsTools(c *gc.C) { 162 model := description.NewModel(description.ModelArgs{ 163 Owner: names.NewUserTag("me"), 164 }) 165 model.AddService(description.ServiceArgs{ 166 Tag: names.NewServiceTag("magic"), 167 CharmURL: "local:trusty/magic", 168 }) 169 model.AddService(description.ServiceArgs{ 170 Tag: names.NewServiceTag("magic"), 171 CharmURL: "cs:trusty/postgresql-42", 172 }) 173 174 uploader := &fakeUploader{charms: make(map[string]string)} 175 config := migration.UploadBinariesConfig{ 176 State: &fakeStateStorage{}, 177 Model: model, 178 Target: &fakeAPIConnection{}, 179 GetCharmUploader: func(api.Connection) migration.CharmUploader { return uploader }, 180 GetToolsUploader: func(target api.Connection) migration.ToolsUploader { return &noOpUploader{} }, 181 GetStateStorage: func(migration.UploadBackend) storage.Storage { return &fakeCharmsStorage{} }, 182 GetCharmStoragePath: func(_ migration.UploadBackend, u *charm.URL) (string, error) { 183 return "/path/for/" + u.String(), nil 184 }, 185 } 186 err := migration.UploadBinaries(config) 187 c.Assert(err, jc.ErrorIsNil) 188 189 c.Assert(uploader.charms, jc.DeepEquals, map[string]string{ 190 "local:trusty/magic": "fake file at /path/for/local:trusty/magic", 191 "cs:trusty/postgresql-42": "fake file at /path/for/cs:trusty/postgresql-42", 192 }) 193 } 194 195 type fakeStateStorage struct { 196 tools fakeToolsStorage 197 charms fakeCharmsStorage 198 } 199 200 type fakeCharmsStorage struct { 201 storage.Storage 202 } 203 204 type fakeAPIConnection struct { 205 api.Connection 206 } 207 208 type fakeToolsStorage struct { 209 binarystorage.Storage 210 closed bool 211 } 212 213 func (f *fakeStateStorage) ToolsStorage() (binarystorage.StorageCloser, error) { 214 return &f.tools, nil 215 } 216 217 func (f *fakeStateStorage) ModelUUID() string { 218 return testing.ModelTag.Id() 219 } 220 221 func (f *fakeStateStorage) MongoSession() *mgo.Session { 222 return nil 223 } 224 225 func (f *fakeStateStorage) Charm(*charm.URL) (*state.Charm, error) { 226 return nil, nil 227 } 228 229 func (f *fakeToolsStorage) Open(v string) (binarystorage.Metadata, io.ReadCloser, error) { 230 buff := bytes.NewBufferString(fmt.Sprintf("fake tools %s", v)) 231 return binarystorage.Metadata{}, ioutil.NopCloser(buff), nil 232 } 233 234 func (f *fakeToolsStorage) Close() error { 235 f.closed = true 236 return nil 237 } 238 239 func (f *fakeCharmsStorage) Get(path string) (io.ReadCloser, int64, error) { 240 buff := bytes.NewBufferString(fmt.Sprintf("fake file at %s", path)) 241 return ioutil.NopCloser(buff), int64(buff.Len()), nil 242 } 243 244 type fakeUploader struct { 245 tools map[version.Binary]string 246 charms map[string]string 247 } 248 249 func (f *fakeUploader) UploadTools(r io.ReadSeeker, v version.Binary, _ ...string) (tools.List, error) { 250 data, err := ioutil.ReadAll(r) 251 if err != nil { 252 return nil, errors.Trace(err) 253 } 254 255 f.tools[v] = string(data) 256 257 uploaded := &tools.Tools{ 258 Version: v, 259 } 260 return tools.List{uploaded}, nil 261 } 262 263 func (f *fakeUploader) UploadCharm(u *charm.URL, r io.ReadSeeker) (*charm.URL, error) { 264 data, err := ioutil.ReadAll(r) 265 if err != nil { 266 return nil, errors.Trace(err) 267 } 268 269 f.charms[u.String()] = string(data) 270 return u, nil 271 } 272 273 type noOpUploader struct{} 274 275 func (*noOpUploader) UploadCharm(*charm.URL, io.ReadSeeker) (*charm.URL, error) { 276 return nil, nil 277 } 278 279 func (*noOpUploader) UploadTools(io.ReadSeeker, version.Binary, ...string) (tools.List, error) { 280 return nil, nil 281 } 282 283 type ExportSuite struct { 284 statetesting.StateSuite 285 } 286 287 var _ = gc.Suite(&ExportSuite{}) 288 289 func (s *ExportSuite) TestExportModel(c *gc.C) { 290 bytes, err := migration.ExportModel(s.State) 291 c.Assert(err, jc.ErrorIsNil) 292 // The bytes must be a valid model. 293 _, err = description.Deserialize(bytes) 294 c.Assert(err, jc.ErrorIsNil) 295 } 296 297 type PrecheckSuite struct { 298 testing.BaseSuite 299 } 300 301 var _ = gc.Suite(&PrecheckSuite{}) 302 303 // Assert that *state.State implements the PrecheckBackend 304 var _ migration.PrecheckBackend = (*state.State)(nil) 305 306 func (*PrecheckSuite) TestPrecheckCleanups(c *gc.C) { 307 backend := &fakePrecheckBackend{} 308 err := migration.Precheck(backend) 309 c.Assert(err, jc.ErrorIsNil) 310 } 311 312 func (*PrecheckSuite) TestPrecheckCleanupsError(c *gc.C) { 313 backend := &fakePrecheckBackend{ 314 cleanupError: errors.New("boom"), 315 } 316 err := migration.Precheck(backend) 317 c.Assert(err, gc.ErrorMatches, "precheck cleanups: boom") 318 } 319 320 func (*PrecheckSuite) TestPrecheckCleanupsNeeded(c *gc.C) { 321 backend := &fakePrecheckBackend{ 322 cleanupNeeded: true, 323 } 324 err := migration.Precheck(backend) 325 c.Assert(err, gc.ErrorMatches, "precheck failed: cleanup needed") 326 } 327 328 type fakePrecheckBackend struct { 329 cleanupNeeded bool 330 cleanupError error 331 } 332 333 func (f *fakePrecheckBackend) NeedsCleanup() (bool, error) { 334 return f.cleanupNeeded, f.cleanupError 335 } 336 337 type InternalSuite struct { 338 testing.BaseSuite 339 } 340 341 var _ = gc.Suite(&InternalSuite{}) 342 343 func (s *InternalSuite) TestControllerValues(c *gc.C) { 344 config := testing.ModelConfig(c) 345 fields := migration.ControllerValues(config) 346 c.Assert(fields, jc.DeepEquals, map[string]interface{}{ 347 "controller-uuid": "deadbeef-0bad-400d-8000-4b1d0d06f00d", 348 "state-port": 19034, 349 "api-port": 17777, 350 "ca-cert": testing.CACert, 351 }) 352 } 353 354 func (s *InternalSuite) TestUpdateConfigFromProvider(c *gc.C) { 355 controllerConfig := testing.ModelConfig(c) 356 configAttrs := testing.FakeConfig() 357 configAttrs["type"] = "dummy" 358 // Fake the "state-id" so the provider thinks it is prepared already. 359 configAttrs["state-id"] = "42" 360 // We need to specify a valid provider type, so we use dummy. 361 // The dummy provider grabs the UUID from the controller config 362 // and returns it in the map with the key "controller-uuid", similar 363 // to what the azure provider will need to do. 364 model := description.NewModel(description.ModelArgs{ 365 Owner: names.NewUserTag("test-admin"), 366 Config: configAttrs, 367 }) 368 369 err := migration.UpdateConfigFromProvider(model, controllerConfig) 370 c.Assert(err, jc.ErrorIsNil) 371 372 modelConfig := model.Config() 373 c.Assert(modelConfig["controller-uuid"], gc.Equals, controllerConfig.UUID()) 374 } 375 376 type CharmInternalSuite struct { 377 statetesting.StateSuite 378 } 379 380 var _ = gc.Suite(&CharmInternalSuite{}) 381 382 func (s *CharmInternalSuite) TestCharmStoragePath(c *gc.C) { 383 charm := s.Factory.MakeCharm(c, nil) 384 385 path, err := migration.GetCharmStoragePath(s.State, charm.URL()) 386 c.Assert(err, jc.ErrorIsNil) 387 c.Assert(path, gc.Equals, "fake-storage-path") 388 }