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  }