github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/watcher/watcher_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package watcher_test 5 6 import ( 7 "time" 8 9 jc "github.com/juju/testing/checkers" 10 "github.com/juju/utils" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/juju/names.v2" 13 "gopkg.in/juju/worker.v1" 14 "gopkg.in/juju/worker.v1/workertest" 15 "gopkg.in/macaroon-bakery.v2-unstable/bakery" 16 "gopkg.in/macaroon-bakery.v2-unstable/bakery/checkers" 17 "gopkg.in/macaroon.v2-unstable" 18 19 "github.com/juju/juju/api" 20 "github.com/juju/juju/api/crossmodelrelations" 21 "github.com/juju/juju/api/migrationminion" 22 "github.com/juju/juju/api/watcher" 23 "github.com/juju/juju/apiserver/params" 24 "github.com/juju/juju/core/crossmodel" 25 "github.com/juju/juju/core/life" 26 "github.com/juju/juju/core/migration" 27 "github.com/juju/juju/core/status" 28 corewatcher "github.com/juju/juju/core/watcher" 29 "github.com/juju/juju/core/watcher/watchertest" 30 "github.com/juju/juju/juju/testing" 31 "github.com/juju/juju/permission" 32 "github.com/juju/juju/state" 33 coretesting "github.com/juju/juju/testing" 34 "github.com/juju/juju/testing/factory" 35 ) 36 37 type watcherSuite struct { 38 testing.JujuConnSuite 39 40 stateAPI api.Connection 41 42 // These are raw State objects. Use them for setup and assertions, but 43 // should never be touched by the API calls themselves 44 rawMachine *state.Machine 45 } 46 47 var _ = gc.Suite(&watcherSuite{}) 48 49 func (s *watcherSuite) SetUpTest(c *gc.C) { 50 s.JujuConnSuite.SetUpTest(c) 51 s.stateAPI, s.rawMachine = s.OpenAPIAsNewMachine(c, state.JobManageModel, state.JobHostUnits) 52 } 53 54 func (s *watcherSuite) TestWatchInitialEventConsumed(c *gc.C) { 55 // Machiner.Watch should send the initial event as part of the Watch 56 // call (for NotifyWatchers there is no state to be transmitted). So a 57 // call to Next() should not have anything to return. 58 var results params.NotifyWatchResults 59 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 60 err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results) 61 c.Assert(err, jc.ErrorIsNil) 62 c.Assert(results.Results, gc.HasLen, 1) 63 result := results.Results[0] 64 c.Assert(result.Error, gc.IsNil) 65 66 // We expect the Call() to "Next" to block, so run it in a goroutine. 67 done := make(chan error) 68 go func() { 69 ignored := struct{}{} 70 done <- s.stateAPI.APICall("NotifyWatcher", s.stateAPI.BestFacadeVersion("NotifyWatcher"), result.NotifyWatcherId, "Next", nil, &ignored) 71 }() 72 73 select { 74 case err := <-done: 75 c.Errorf("Call(Next) did not block immediately after Watch(): err %v", err) 76 case <-time.After(coretesting.ShortWait): 77 } 78 } 79 80 func (s *watcherSuite) TestWatchMachine(c *gc.C) { 81 var results params.NotifyWatchResults 82 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 83 err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results) 84 c.Assert(err, jc.ErrorIsNil) 85 c.Assert(results.Results, gc.HasLen, 1) 86 result := results.Results[0] 87 c.Assert(result.Error, gc.IsNil) 88 89 w := watcher.NewNotifyWatcher(s.stateAPI, result) 90 wc := watchertest.NewNotifyWatcherC(c, w, s.BackingState.StartSync) 91 defer wc.AssertStops() 92 wc.AssertOneChange() 93 } 94 95 func (s *watcherSuite) TestNotifyWatcherStopsWithPendingSend(c *gc.C) { 96 var results params.NotifyWatchResults 97 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 98 err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results) 99 c.Assert(err, jc.ErrorIsNil) 100 c.Assert(results.Results, gc.HasLen, 1) 101 result := results.Results[0] 102 c.Assert(result.Error, gc.IsNil) 103 104 // params.NotifyWatcher conforms to the watcher.NotifyWatcher interface 105 w := watcher.NewNotifyWatcher(s.stateAPI, result) 106 wc := watchertest.NewNotifyWatcherC(c, w, s.BackingState.StartSync) 107 wc.AssertStops() 108 } 109 110 func (s *watcherSuite) TestWatchUnitsKeepsEvents(c *gc.C) { 111 // Create two applications, relate them, and add one unit to each - a 112 // principal and a subordinate. 113 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 114 s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging")) 115 eps, err := s.State.InferEndpoints("mysql", "logging") 116 c.Assert(err, jc.ErrorIsNil) 117 rel, err := s.State.AddRelation(eps...) 118 c.Assert(err, jc.ErrorIsNil) 119 principal, err := mysql.AddUnit(state.AddUnitParams{}) 120 c.Assert(err, jc.ErrorIsNil) 121 err = principal.AssignToMachine(s.rawMachine) 122 c.Assert(err, jc.ErrorIsNil) 123 relUnit, err := rel.Unit(principal) 124 c.Assert(err, jc.ErrorIsNil) 125 err = relUnit.EnterScope(nil) 126 c.Assert(err, jc.ErrorIsNil) 127 subordinate, err := s.State.Unit("logging/0") 128 c.Assert(err, jc.ErrorIsNil) 129 130 // Call the Deployer facade's WatchUnits for machine-0. 131 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 132 var results params.StringsWatchResults 133 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 134 err = s.stateAPI.APICall("Deployer", s.stateAPI.BestFacadeVersion("Deployer"), "", "WatchUnits", args, &results) 135 c.Assert(err, jc.ErrorIsNil) 136 c.Assert(results.Results, gc.HasLen, 1) 137 result := results.Results[0] 138 c.Assert(result.Error, gc.IsNil) 139 140 // Start a StringsWatcher and check the initial event. 141 w := watcher.NewStringsWatcher(s.stateAPI, result) 142 wc := watchertest.NewStringsWatcherC(c, w, s.BackingState.StartSync) 143 defer wc.AssertStops() 144 145 wc.AssertChange("mysql/0", "logging/0") 146 wc.AssertNoChange() 147 148 // Now, without reading any changes advance the lifecycle of both 149 // units. 150 err = subordinate.EnsureDead() 151 c.Assert(err, jc.ErrorIsNil) 152 err = subordinate.Remove() 153 c.Assert(err, jc.ErrorIsNil) 154 err = principal.EnsureDead() 155 c.Assert(err, jc.ErrorIsNil) 156 157 // Expect both changes are passed back. 158 wc.AssertChange("mysql/0", "logging/0") 159 wc.AssertNoChange() 160 } 161 162 func (s *watcherSuite) TestStringsWatcherStopsWithPendingSend(c *gc.C) { 163 // Call the Deployer facade's WatchUnits for machine-0. 164 var results params.StringsWatchResults 165 args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}} 166 err := s.stateAPI.APICall("Deployer", s.stateAPI.BestFacadeVersion("Deployer"), "", "WatchUnits", args, &results) 167 c.Assert(err, jc.ErrorIsNil) 168 c.Assert(results.Results, gc.HasLen, 1) 169 result := results.Results[0] 170 c.Assert(result.Error, gc.IsNil) 171 172 // Start a StringsWatcher and check the initial event. 173 w := watcher.NewStringsWatcher(s.stateAPI, result) 174 wc := watchertest.NewStringsWatcherC(c, w, s.BackingState.StartSync) 175 defer wc.AssertStops() 176 177 // Create an application, deploy a unit of it on the machine. 178 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 179 principal, err := mysql.AddUnit(state.AddUnitParams{}) 180 c.Assert(err, jc.ErrorIsNil) 181 err = principal.AssignToMachine(s.rawMachine) 182 c.Assert(err, jc.ErrorIsNil) 183 } 184 185 // TODO(fwereade): 2015-11-18 lp:1517391 186 func (s *watcherSuite) TestWatchMachineStorage(c *gc.C) { 187 s.Factory.MakeMachine(c, &factory.MachineParams{ 188 Volumes: []state.HostVolumeParams{{ 189 Volume: state.VolumeParams{ 190 Pool: "modelscoped", 191 Size: 1024, 192 }, 193 }}, 194 }) 195 196 var results params.MachineStorageIdsWatchResults 197 args := params.Entities{Entities: []params.Entity{{ 198 Tag: s.Model.ModelTag().String(), 199 }}} 200 err := s.stateAPI.APICall( 201 "StorageProvisioner", 202 s.stateAPI.BestFacadeVersion("StorageProvisioner"), 203 "", "WatchVolumeAttachments", args, &results) 204 c.Assert(err, jc.ErrorIsNil) 205 c.Assert(results.Results, gc.HasLen, 1) 206 result := results.Results[0] 207 c.Assert(result.Error, gc.IsNil) 208 209 w := watcher.NewVolumeAttachmentsWatcher(s.stateAPI, result) 210 defer func() { 211 212 // Check we can stop the watcher... 213 w.Kill() 214 wait := make(chan error) 215 go func() { 216 wait <- w.Wait() 217 }() 218 select { 219 case err := <-wait: 220 c.Assert(err, jc.ErrorIsNil) 221 case <-time.After(coretesting.LongWait): 222 c.Fatalf("watcher never stopped") 223 } 224 225 // ...and that its channel hasn't been closed. 226 s.BackingState.StartSync() 227 select { 228 case change, ok := <-w.Changes(): 229 c.Fatalf("watcher sent unexpected change: (%#v, %v)", change, ok) 230 default: 231 } 232 233 }() 234 235 // Check initial event; 236 s.BackingState.StartSync() 237 select { 238 case changes, ok := <-w.Changes(): 239 c.Assert(ok, jc.IsTrue) 240 c.Assert(changes, jc.SameContents, []corewatcher.MachineStorageId{{ 241 MachineTag: "machine-1", 242 AttachmentTag: "volume-0", 243 }}) 244 case <-time.After(coretesting.LongWait): 245 c.Fatalf("timed out waiting for change") 246 } 247 248 // check no subsequent event. 249 s.BackingState.StartSync() 250 select { 251 case <-w.Changes(): 252 c.Fatalf("received unexpected change") 253 case <-time.After(coretesting.ShortWait): 254 } 255 } 256 257 func (s *watcherSuite) assertSetupRelationStatusWatch( 258 c *gc.C, rel *state.Relation, 259 ) (func(life life.Value, suspended bool, reason string), func()) { 260 // Export the relation so it can be found with a token. 261 re := s.State.RemoteEntities() 262 token, err := re.ExportLocalEntity(rel.Tag()) 263 c.Assert(err, jc.ErrorIsNil) 264 265 // Create the offer connection details. 266 s.Factory.MakeUser(c, &factory.UserParams{Name: "fred"}) 267 offers := state.NewApplicationOffers(s.State) 268 offer, err := offers.AddOffer(crossmodel.AddApplicationOfferArgs{ 269 OfferName: "hosted-mysql", 270 ApplicationName: "mysql", 271 Owner: "admin", 272 }) 273 c.Assert(err, jc.ErrorIsNil) 274 _, err = s.State.AddOfferConnection(state.AddOfferConnectionParams{ 275 OfferUUID: offer.OfferUUID, 276 Username: "fred", 277 RelationKey: rel.String(), 278 RelationId: rel.Id(), 279 SourceModelUUID: s.State.ModelUUID(), 280 }) 281 c.Assert(err, jc.ErrorIsNil) 282 283 // Add the consume permission for the offer so the macaroon 284 // discharge can occur. 285 err = s.State.CreateOfferAccess( 286 names.NewApplicationOfferTag("hosted-mysql"), 287 names.NewUserTag("fred"), permission.ConsumeAccess) 288 c.Assert(err, jc.ErrorIsNil) 289 290 // Create a macaroon for authorisation. 291 store, err := s.State.NewBakeryStorage() 292 c.Assert(err, jc.ErrorIsNil) 293 bakery, err := bakery.NewService(bakery.NewServiceParams{ 294 Location: "juju model " + s.State.ModelUUID(), 295 Store: store, 296 }) 297 c.Assert(err, jc.ErrorIsNil) 298 mac, err := bakery.NewMacaroon( 299 []checkers.Caveat{ 300 checkers.DeclaredCaveat("source-model-uuid", s.State.ModelUUID()), 301 checkers.DeclaredCaveat("relation-key", rel.String()), 302 checkers.DeclaredCaveat("username", "fred"), 303 }) 304 c.Assert(err, jc.ErrorIsNil) 305 306 // Start watching for a relation change. 307 client := crossmodelrelations.NewClient(s.stateAPI) 308 w, err := client.WatchRelationSuspendedStatus(params.RemoteEntityArg{ 309 Token: token, 310 Macaroons: macaroon.Slice{mac}, 311 }) 312 c.Assert(err, jc.ErrorIsNil) 313 stop := func() { 314 workertest.CleanKill(c, w) 315 } 316 modelUUID := s.BackingState.ModelUUID() 317 assertNoChange := func() { 318 s.WaitForModelWatchersIdle(c, modelUUID) 319 select { 320 case _, ok := <-w.Changes(): 321 c.Fatalf("watcher sent unexpected change: (_, %v)", ok) 322 case <-time.After(coretesting.ShortWait): 323 } 324 } 325 326 assertChange := func(life life.Value, suspended bool, reason string) { 327 s.WaitForModelWatchersIdle(c, modelUUID) 328 select { 329 case changes, ok := <-w.Changes(): 330 c.Check(ok, jc.IsTrue) 331 c.Check(changes, gc.HasLen, 1) 332 c.Check(changes[0].Life, gc.Equals, life) 333 c.Check(changes[0].Suspended, gc.Equals, suspended) 334 c.Check(changes[0].SuspendedReason, gc.Equals, reason) 335 case <-time.After(coretesting.LongWait): 336 c.Fatalf("watcher didn't emit an event") 337 } 338 assertNoChange() 339 } 340 341 // Initial event. 342 assertChange(life.Alive, false, "") 343 return assertChange, stop 344 } 345 346 func (s *watcherSuite) TestRelationStatusWatcher(c *gc.C) { 347 // Create a pair of services and a relation between them. 348 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 349 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 350 eps, err := s.State.InferEndpoints("wordpress", "mysql") 351 c.Assert(err, jc.ErrorIsNil) 352 rel, err := s.State.AddRelation(eps...) 353 c.Assert(err, jc.ErrorIsNil) 354 355 u, err := mysql.AddUnit(state.AddUnitParams{}) 356 c.Assert(err, jc.ErrorIsNil) 357 m := s.Factory.MakeMachine(c, &factory.MachineParams{}) 358 err = u.AssignToMachine(m) 359 c.Assert(err, jc.ErrorIsNil) 360 relUnit, err := rel.Unit(u) 361 c.Assert(err, jc.ErrorIsNil) 362 err = relUnit.EnterScope(nil) 363 c.Assert(err, jc.ErrorIsNil) 364 365 assertChange, stop := s.assertSetupRelationStatusWatch(c, rel) 366 defer stop() 367 368 err = rel.SetSuspended(true, "another reason") 369 c.Assert(err, jc.ErrorIsNil) 370 assertChange(life.Alive, true, "another reason") 371 372 err = rel.SetSuspended(false, "") 373 c.Assert(err, jc.ErrorIsNil) 374 assertChange(life.Alive, false, "") 375 376 err = rel.Destroy() 377 c.Assert(err, jc.ErrorIsNil) 378 assertChange(life.Dying, false, "") 379 } 380 381 func (s *watcherSuite) TestRelationStatusWatcherDeadRelation(c *gc.C) { 382 // Create a pair of services and a relation between them. 383 s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 384 s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 385 eps, err := s.State.InferEndpoints("wordpress", "mysql") 386 c.Assert(err, jc.ErrorIsNil) 387 rel, err := s.State.AddRelation(eps...) 388 c.Assert(err, jc.ErrorIsNil) 389 390 assertChange, stop := s.assertSetupRelationStatusWatch(c, rel) 391 defer stop() 392 393 err = rel.Destroy() 394 c.Assert(err, jc.ErrorIsNil) 395 assertChange(life.Dead, false, "") 396 } 397 398 func (s *watcherSuite) setupOfferStatusWatch( 399 c *gc.C, 400 ) (func(status status.Status, message string), func()) { 401 // Create the offer connection details. 402 s.Factory.MakeUser(c, &factory.UserParams{Name: "fred"}) 403 offers := state.NewApplicationOffers(s.State) 404 offer, err := offers.AddOffer(crossmodel.AddApplicationOfferArgs{ 405 OfferName: "hosted-mysql", 406 ApplicationName: "mysql", 407 Owner: "admin", 408 }) 409 c.Assert(err, jc.ErrorIsNil) 410 411 // Add the consume permission for the offer so the macaroon 412 // discharge can occur. 413 err = s.State.CreateOfferAccess( 414 names.NewApplicationOfferTag("hosted-mysql"), 415 names.NewUserTag("fred"), permission.ConsumeAccess) 416 c.Assert(err, jc.ErrorIsNil) 417 418 // Create a macaroon for authorisation. 419 store, err := s.State.NewBakeryStorage() 420 c.Assert(err, jc.ErrorIsNil) 421 bakery, err := bakery.NewService(bakery.NewServiceParams{ 422 Location: "juju model " + s.State.ModelUUID(), 423 Store: store, 424 }) 425 c.Assert(err, jc.ErrorIsNil) 426 mac, err := bakery.NewMacaroon( 427 []checkers.Caveat{ 428 checkers.DeclaredCaveat("source-model-uuid", s.State.ModelUUID()), 429 checkers.DeclaredCaveat("offer-uuid", offer.OfferUUID), 430 checkers.DeclaredCaveat("username", "fred"), 431 }) 432 c.Assert(err, jc.ErrorIsNil) 433 434 s.WaitForModelWatchersIdle(c, s.Model.UUID()) 435 // Start watching for a relation change. 436 client := crossmodelrelations.NewClient(s.stateAPI) 437 w, err := client.WatchOfferStatus(params.OfferArg{ 438 OfferUUID: offer.OfferUUID, 439 Macaroons: macaroon.Slice{mac}, 440 }) 441 c.Assert(err, jc.ErrorIsNil) 442 stop := func() { 443 workertest.CleanKill(c, w) 444 } 445 446 assertNoChange := func() { 447 s.BackingState.StartSync() 448 select { 449 case _, ok := <-w.Changes(): 450 c.Fatalf("watcher sent unexpected change: (_, %v)", ok) 451 case <-time.After(coretesting.ShortWait): 452 } 453 } 454 455 assertChange := func(status status.Status, message string) { 456 s.BackingState.StartSync() 457 select { 458 case changes, ok := <-w.Changes(): 459 c.Check(ok, jc.IsTrue) 460 if status == "" { 461 c.Assert(changes, gc.HasLen, 0) 462 break 463 } 464 c.Assert(changes, gc.HasLen, 1) 465 c.Check(changes[0].Name, gc.Equals, "hosted-mysql") 466 c.Check(changes[0].Status.Status, gc.Equals, status) 467 c.Check(changes[0].Status.Message, gc.Equals, message) 468 case <-time.After(coretesting.LongWait): 469 c.Fatalf("watcher didn't emit an event") 470 } 471 assertNoChange() 472 } 473 474 // Initial event. 475 assertChange(status.Waiting, "waiting for machine") 476 return assertChange, stop 477 } 478 479 func (s *watcherSuite) TestOfferStatusWatcher(c *gc.C) { 480 // Create a pair of services and a relation between them. 481 mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql")) 482 483 assertChange, stop := s.setupOfferStatusWatch(c) 484 defer stop() 485 486 err := mysql.SetStatus(status.StatusInfo{Status: status.Waiting, Message: "another message"}) 487 c.Assert(err, jc.ErrorIsNil) 488 assertChange(status.Waiting, "another message") 489 490 // Deleting the offer results in an empty change set. 491 offers := state.NewApplicationOffers(s.State) 492 err = offers.Remove("hosted-mysql", false) 493 c.Assert(err, jc.ErrorIsNil) 494 err = mysql.Destroy() 495 c.Assert(err, jc.ErrorIsNil) 496 assertChange("", "") 497 } 498 499 type migrationSuite struct { 500 testing.JujuConnSuite 501 } 502 503 var _ = gc.Suite(&migrationSuite{}) 504 505 func (s *migrationSuite) startSync(c *gc.C, st *state.State) { 506 backingSt, err := s.BackingStatePool.Get(st.ModelUUID()) 507 c.Assert(err, jc.ErrorIsNil) 508 backingSt.StartSync() 509 backingSt.Release() 510 } 511 512 func (s *migrationSuite) TestMigrationStatusWatcher(c *gc.C) { 513 const nonce = "noncey" 514 515 // Create a model to migrate. 516 hostedState := s.Factory.MakeModel(c, &factory.ModelParams{}) 517 defer hostedState.Close() 518 hostedFactory := factory.NewFactory(hostedState, s.StatePool) 519 520 // Create a machine in the hosted model to connect as. 521 m, password := hostedFactory.MakeMachineReturningPassword(c, &factory.MachineParams{ 522 Nonce: nonce, 523 }) 524 525 // Connect as the machine to watch for migration status. 526 apiInfo := s.APIInfo(c) 527 apiInfo.Tag = m.Tag() 528 apiInfo.Password = password 529 530 hostedModel, err := hostedState.Model() 531 c.Assert(err, jc.ErrorIsNil) 532 533 apiInfo.ModelTag = hostedModel.ModelTag() 534 apiInfo.Nonce = nonce 535 536 apiConn, err := api.Open(apiInfo, api.DialOpts{}) 537 c.Assert(err, jc.ErrorIsNil) 538 defer apiConn.Close() 539 540 // Start watching for a migration. 541 client := migrationminion.NewClient(apiConn) 542 w, err := client.Watch() 543 c.Assert(err, jc.ErrorIsNil) 544 defer func() { 545 c.Assert(worker.Stop(w), jc.ErrorIsNil) 546 }() 547 548 assertNoChange := func() { 549 s.startSync(c, hostedState) 550 select { 551 case _, ok := <-w.Changes(): 552 c.Fatalf("watcher sent unexpected change: (_, %v)", ok) 553 case <-time.After(coretesting.ShortWait): 554 } 555 } 556 557 assertChange := func(id string, phase migration.Phase) { 558 s.startSync(c, hostedState) 559 select { 560 case status, ok := <-w.Changes(): 561 c.Assert(ok, jc.IsTrue) 562 c.Check(status.MigrationId, gc.Equals, id) 563 c.Check(status.Phase, gc.Equals, phase) 564 case <-time.After(coretesting.LongWait): 565 c.Fatalf("watcher didn't emit an event") 566 } 567 assertNoChange() 568 } 569 570 // Initial event with no migration in progress. 571 assertChange("", migration.NONE) 572 573 // Now create a migration, should trigger watcher. 574 spec := state.MigrationSpec{ 575 InitiatedBy: names.NewUserTag("someone"), 576 TargetInfo: migration.TargetInfo{ 577 ControllerTag: names.NewControllerTag(utils.MustNewUUID().String()), 578 Addrs: []string{"1.2.3.4:5"}, 579 CACert: "cert", 580 AuthTag: names.NewUserTag("dog"), 581 Password: "sekret", 582 }, 583 } 584 mig, err := hostedState.CreateMigration(spec) 585 c.Assert(err, jc.ErrorIsNil) 586 assertChange(mig.Id(), migration.QUIESCE) 587 588 // Now abort the migration, this should be reported too. 589 c.Assert(mig.SetPhase(migration.ABORT), jc.ErrorIsNil) 590 assertChange(mig.Id(), migration.ABORT) 591 c.Assert(mig.SetPhase(migration.ABORTDONE), jc.ErrorIsNil) 592 assertChange(mig.Id(), migration.ABORTDONE) 593 594 // Start a new migration, this should also trigger. 595 mig2, err := hostedState.CreateMigration(spec) 596 c.Assert(err, jc.ErrorIsNil) 597 assertChange(mig2.Id(), migration.QUIESCE) 598 }