github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"context"
     8  	"time"
     9  
    10  	"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery"
    11  	"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers"
    12  	"github.com/juju/charm/v12"
    13  	"github.com/juju/names/v5"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils/v3"
    16  	"github.com/juju/worker/v3"
    17  	"github.com/juju/worker/v3/workertest"
    18  	gc "gopkg.in/check.v1"
    19  	"gopkg.in/macaroon.v2"
    20  
    21  	"github.com/juju/juju/api"
    22  	"github.com/juju/juju/api/agent/migrationminion"
    23  	"github.com/juju/juju/api/agent/secretsmanager"
    24  	"github.com/juju/juju/api/controller/crossmodelrelations"
    25  	"github.com/juju/juju/api/watcher"
    26  	"github.com/juju/juju/core/crossmodel"
    27  	"github.com/juju/juju/core/life"
    28  	"github.com/juju/juju/core/migration"
    29  	"github.com/juju/juju/core/permission"
    30  	"github.com/juju/juju/core/secrets"
    31  	"github.com/juju/juju/core/status"
    32  	corewatcher "github.com/juju/juju/core/watcher"
    33  	"github.com/juju/juju/core/watcher/watchertest"
    34  	"github.com/juju/juju/juju/testing"
    35  	"github.com/juju/juju/rpc/params"
    36  	"github.com/juju/juju/state"
    37  	coretesting "github.com/juju/juju/testing"
    38  	"github.com/juju/juju/testing/factory"
    39  )
    40  
    41  type watcherSuite struct {
    42  	testing.JujuConnSuite
    43  
    44  	stateAPI api.Connection
    45  
    46  	// These are raw State objects. Use them for setup and assertions, but
    47  	// should never be touched by the API calls themselves
    48  	rawMachine *state.Machine
    49  }
    50  
    51  var _ = gc.Suite(&watcherSuite{})
    52  
    53  func (s *watcherSuite) SetUpTest(c *gc.C) {
    54  	s.JujuConnSuite.SetUpTest(c)
    55  	s.stateAPI, s.rawMachine = s.OpenAPIAsNewMachine(c, state.JobManageModel, state.JobHostUnits)
    56  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
    57  }
    58  
    59  func (s *watcherSuite) TestWatchInitialEventConsumed(c *gc.C) {
    60  	// Machiner.Watch should send the initial event as part of the Watch
    61  	// call (for NotifyWatchers there is no state to be transmitted). So a
    62  	// call to Next() should not have anything to return.
    63  	var results params.NotifyWatchResults
    64  	args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}}
    65  	err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results)
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	c.Assert(results.Results, gc.HasLen, 1)
    68  	result := results.Results[0]
    69  	c.Assert(result.Error, gc.IsNil)
    70  
    71  	// We expect the Call() to "Next" to block, so run it in a goroutine.
    72  	done := make(chan error)
    73  	go func() {
    74  		ignored := struct{}{}
    75  		done <- s.stateAPI.APICall("NotifyWatcher", s.stateAPI.BestFacadeVersion("NotifyWatcher"), result.NotifyWatcherId, "Next", nil, &ignored)
    76  	}()
    77  
    78  	select {
    79  	case err := <-done:
    80  		c.Errorf("Call(Next) did not block immediately after Watch(): err %v", err)
    81  	case <-time.After(coretesting.ShortWait):
    82  	}
    83  }
    84  
    85  func (s *watcherSuite) TestWatchMachine(c *gc.C) {
    86  	var results params.NotifyWatchResults
    87  	args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}}
    88  	err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results)
    89  	c.Assert(err, jc.ErrorIsNil)
    90  	c.Assert(results.Results, gc.HasLen, 1)
    91  	result := results.Results[0]
    92  	c.Assert(result.Error, gc.IsNil)
    93  
    94  	w := watcher.NewNotifyWatcher(s.stateAPI, result)
    95  	wc := watchertest.NewNotifyWatcherC(c, w)
    96  	defer wc.AssertStops()
    97  	wc.AssertOneChange()
    98  }
    99  
   100  func (s *watcherSuite) TestNotifyWatcherStopsWithPendingSend(c *gc.C) {
   101  	var results params.NotifyWatchResults
   102  	args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}}
   103  	err := s.stateAPI.APICall("Machiner", s.stateAPI.BestFacadeVersion("Machiner"), "", "Watch", args, &results)
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	c.Assert(results.Results, gc.HasLen, 1)
   106  	result := results.Results[0]
   107  	c.Assert(result.Error, gc.IsNil)
   108  
   109  	// params.NotifyWatcher conforms to the watcher.NotifyWatcher interface
   110  	w := watcher.NewNotifyWatcher(s.stateAPI, result)
   111  	wc := watchertest.NewNotifyWatcherC(c, w)
   112  	wc.AssertStops()
   113  }
   114  
   115  func (s *watcherSuite) TestWatchUnitsKeepsEvents(c *gc.C) {
   116  	// Create two applications, relate them, and add one unit to each - a
   117  	// principal and a subordinate.
   118  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   119  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
   120  	eps, err := s.State.InferEndpoints("mysql", "logging")
   121  	c.Assert(err, jc.ErrorIsNil)
   122  	rel, err := s.State.AddRelation(eps...)
   123  	c.Assert(err, jc.ErrorIsNil)
   124  	principal, err := mysql.AddUnit(state.AddUnitParams{})
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	err = principal.AssignToMachine(s.rawMachine)
   127  	c.Assert(err, jc.ErrorIsNil)
   128  	relUnit, err := rel.Unit(principal)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	err = relUnit.EnterScope(nil)
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	subordinate, err := s.State.Unit("logging/0")
   133  	c.Assert(err, jc.ErrorIsNil)
   134  
   135  	// Call the Deployer facade's WatchUnits for machine-0.
   136  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
   137  	var results params.StringsWatchResults
   138  	args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}}
   139  	err = s.stateAPI.APICall("Deployer", s.stateAPI.BestFacadeVersion("Deployer"), "", "WatchUnits", args, &results)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	c.Assert(results.Results, gc.HasLen, 1)
   142  	result := results.Results[0]
   143  	c.Assert(result.Error, gc.IsNil)
   144  
   145  	// Start a StringsWatcher and check the initial event.
   146  	w := watcher.NewStringsWatcher(s.stateAPI, result)
   147  	wc := watchertest.NewStringsWatcherC(c, w)
   148  	defer wc.AssertStops()
   149  
   150  	wc.AssertChange("mysql/0", "logging/0")
   151  	wc.AssertNoChange()
   152  
   153  	// Now, without reading any changes advance the lifecycle of both
   154  	// units.
   155  	err = subordinate.EnsureDead()
   156  	c.Assert(err, jc.ErrorIsNil)
   157  	err = subordinate.Remove()
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	err = principal.EnsureDead()
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	// Expect both changes are passed back.
   163  	wc.AssertChange("mysql/0", "logging/0")
   164  	wc.AssertNoChange()
   165  }
   166  
   167  func (s *watcherSuite) TestStringsWatcherStopsWithPendingSend(c *gc.C) {
   168  	// Call the Deployer facade's WatchUnits for machine-0.
   169  	var results params.StringsWatchResults
   170  	args := params.Entities{Entities: []params.Entity{{Tag: s.rawMachine.Tag().String()}}}
   171  	err := s.stateAPI.APICall("Deployer", s.stateAPI.BestFacadeVersion("Deployer"), "", "WatchUnits", args, &results)
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	c.Assert(results.Results, gc.HasLen, 1)
   174  	result := results.Results[0]
   175  	c.Assert(result.Error, gc.IsNil)
   176  
   177  	// Start a StringsWatcher and check the initial event.
   178  	w := watcher.NewStringsWatcher(s.stateAPI, result)
   179  	wc := watchertest.NewStringsWatcherC(c, w)
   180  	defer wc.AssertStops()
   181  
   182  	// Create an application, deploy a unit of it on the machine.
   183  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   184  	principal, err := mysql.AddUnit(state.AddUnitParams{})
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	err = principal.AssignToMachine(s.rawMachine)
   187  	c.Assert(err, jc.ErrorIsNil)
   188  }
   189  
   190  // TODO(fwereade): 2015-11-18 lp:1517391
   191  func (s *watcherSuite) TestWatchMachineStorage(c *gc.C) {
   192  	s.Factory.MakeMachine(c, &factory.MachineParams{
   193  		Volumes: []state.HostVolumeParams{{
   194  			Volume: state.VolumeParams{
   195  				Pool: "modelscoped",
   196  				Size: 1024,
   197  			},
   198  		}},
   199  	})
   200  
   201  	var results params.MachineStorageIdsWatchResults
   202  	args := params.Entities{Entities: []params.Entity{{
   203  		Tag: s.Model.ModelTag().String(),
   204  	}}}
   205  	err := s.stateAPI.APICall(
   206  		"StorageProvisioner",
   207  		s.stateAPI.BestFacadeVersion("StorageProvisioner"),
   208  		"", "WatchVolumeAttachments", args, &results)
   209  	c.Assert(err, jc.ErrorIsNil)
   210  	c.Assert(results.Results, gc.HasLen, 1)
   211  	result := results.Results[0]
   212  	c.Assert(result.Error, gc.IsNil)
   213  
   214  	w := watcher.NewVolumeAttachmentsWatcher(s.stateAPI, result)
   215  	defer func() {
   216  
   217  		// Check we can stop the watcher...
   218  		w.Kill()
   219  		wait := make(chan error)
   220  		go func() {
   221  			wait <- w.Wait()
   222  		}()
   223  		select {
   224  		case err := <-wait:
   225  			c.Assert(err, jc.ErrorIsNil)
   226  		case <-time.After(coretesting.LongWait):
   227  			c.Fatalf("watcher never stopped")
   228  		}
   229  
   230  		// ...and that its channel hasn't been closed.
   231  		select {
   232  		case change, ok := <-w.Changes():
   233  			c.Fatalf("watcher sent unexpected change: (%#v, %v)", change, ok)
   234  		default:
   235  		}
   236  
   237  	}()
   238  
   239  	// Check initial event;
   240  	select {
   241  	case changes, ok := <-w.Changes():
   242  		c.Assert(ok, jc.IsTrue)
   243  		c.Assert(changes, jc.SameContents, []corewatcher.MachineStorageId{{
   244  			MachineTag:    "machine-1",
   245  			AttachmentTag: "volume-0",
   246  		}})
   247  	case <-time.After(coretesting.LongWait):
   248  		c.Fatalf("timed out waiting for change")
   249  	}
   250  
   251  	// check no subsequent event.
   252  	select {
   253  	case <-w.Changes():
   254  		c.Fatalf("received unexpected change")
   255  	case <-time.After(coretesting.ShortWait):
   256  	}
   257  }
   258  
   259  func (s *watcherSuite) assertSetupRelationStatusWatch(
   260  	c *gc.C, rel *state.Relation,
   261  ) (func(life life.Value, suspended bool, reason string), func()) {
   262  	// Export the relation so it can be found with a token.
   263  	re := s.State.RemoteEntities()
   264  	token, err := re.ExportLocalEntity(rel.Tag())
   265  	c.Assert(err, jc.ErrorIsNil)
   266  
   267  	// Create the offer connection details.
   268  	s.Factory.MakeUser(c, &factory.UserParams{Name: "fred"})
   269  	offers := state.NewApplicationOffers(s.State)
   270  	offer, err := offers.AddOffer(crossmodel.AddApplicationOfferArgs{
   271  		OfferName:       "hosted-mysql",
   272  		ApplicationName: "mysql",
   273  		Owner:           "admin",
   274  	})
   275  	c.Assert(err, jc.ErrorIsNil)
   276  	_, err = s.State.AddOfferConnection(state.AddOfferConnectionParams{
   277  		OfferUUID:       offer.OfferUUID,
   278  		Username:        "fred",
   279  		RelationKey:     rel.String(),
   280  		RelationId:      rel.Id(),
   281  		SourceModelUUID: s.State.ModelUUID(),
   282  	})
   283  	c.Assert(err, jc.ErrorIsNil)
   284  
   285  	// Add the consume permission for the offer so the macaroon
   286  	// discharge can occur.
   287  	err = s.State.CreateOfferAccess(
   288  		names.NewApplicationOfferTag(offer.OfferUUID),
   289  		names.NewUserTag("fred"), permission.ConsumeAccess)
   290  	c.Assert(err, jc.ErrorIsNil)
   291  
   292  	// Create a macaroon for authorisation.
   293  	store, err := s.State.NewBakeryStorage()
   294  	c.Assert(err, jc.ErrorIsNil)
   295  	b := bakery.New(bakery.BakeryParams{
   296  		RootKeyStore: store,
   297  	})
   298  	c.Assert(err, jc.ErrorIsNil)
   299  	mac, err := b.Oven.NewMacaroon(
   300  		context.Background(),
   301  		bakery.LatestVersion,
   302  		[]checkers.Caveat{
   303  			checkers.DeclaredCaveat("source-model-uuid", s.State.ModelUUID()),
   304  			checkers.DeclaredCaveat("relation-key", rel.String()),
   305  			checkers.DeclaredCaveat("username", "fred"),
   306  		}, bakery.NoOp)
   307  	c.Assert(err, jc.ErrorIsNil)
   308  
   309  	// Start watching for a relation change.
   310  	client := crossmodelrelations.NewClient(s.stateAPI)
   311  	w, err := client.WatchRelationSuspendedStatus(params.RemoteEntityArg{
   312  		Token:     token,
   313  		Macaroons: macaroon.Slice{mac.M()},
   314  	})
   315  	c.Assert(err, jc.ErrorIsNil)
   316  	stop := func() {
   317  		workertest.CleanKill(c, w)
   318  	}
   319  	modelUUID := s.BackingState.ModelUUID()
   320  	assertNoChange := func() {
   321  		s.WaitForModelWatchersIdle(c, modelUUID)
   322  		select {
   323  		case _, ok := <-w.Changes():
   324  			c.Fatalf("watcher sent unexpected change: (_, %v)", ok)
   325  		case <-time.After(coretesting.ShortWait):
   326  		}
   327  	}
   328  
   329  	assertChange := func(life life.Value, suspended bool, reason string) {
   330  		s.WaitForModelWatchersIdle(c, modelUUID)
   331  		select {
   332  		case changes, ok := <-w.Changes():
   333  			c.Check(ok, jc.IsTrue)
   334  			c.Check(changes, gc.HasLen, 1)
   335  			c.Check(changes[0].Life, gc.Equals, life)
   336  			c.Check(changes[0].Suspended, gc.Equals, suspended)
   337  			c.Check(changes[0].SuspendedReason, gc.Equals, reason)
   338  		case <-time.After(coretesting.LongWait):
   339  			c.Fatalf("watcher didn't emit an event")
   340  		}
   341  		assertNoChange()
   342  	}
   343  
   344  	// Initial event.
   345  	assertChange(life.Alive, false, "")
   346  	return assertChange, stop
   347  }
   348  
   349  func (s *watcherSuite) TestRelationStatusWatcher(c *gc.C) {
   350  	// Create a pair of services and a relation between them.
   351  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   352  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   353  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
   354  	c.Assert(err, jc.ErrorIsNil)
   355  	rel, err := s.State.AddRelation(eps...)
   356  	c.Assert(err, jc.ErrorIsNil)
   357  
   358  	u, err := mysql.AddUnit(state.AddUnitParams{})
   359  	c.Assert(err, jc.ErrorIsNil)
   360  	m := s.Factory.MakeMachine(c, &factory.MachineParams{})
   361  	err = u.AssignToMachine(m)
   362  	c.Assert(err, jc.ErrorIsNil)
   363  	relUnit, err := rel.Unit(u)
   364  	c.Assert(err, jc.ErrorIsNil)
   365  	err = relUnit.EnterScope(nil)
   366  	c.Assert(err, jc.ErrorIsNil)
   367  
   368  	// Ensure that all the creation events have flowed through the system.
   369  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
   370  
   371  	assertChange, stop := s.assertSetupRelationStatusWatch(c, rel)
   372  	defer stop()
   373  
   374  	err = rel.SetSuspended(true, "another reason")
   375  	c.Assert(err, jc.ErrorIsNil)
   376  	assertChange(life.Alive, true, "another reason")
   377  
   378  	err = rel.SetSuspended(false, "")
   379  	c.Assert(err, jc.ErrorIsNil)
   380  	assertChange(life.Alive, false, "")
   381  
   382  	err = rel.Refresh()
   383  	c.Assert(err, jc.ErrorIsNil)
   384  	err = rel.Destroy()
   385  	c.Assert(err, jc.ErrorIsNil)
   386  	assertChange(life.Dying, false, "")
   387  }
   388  
   389  func (s *watcherSuite) TestRelationStatusWatcherDeadRelation(c *gc.C) {
   390  	// Create a pair of services and a relation between them.
   391  	s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   392  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   393  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
   394  	c.Assert(err, jc.ErrorIsNil)
   395  	rel, err := s.State.AddRelation(eps...)
   396  	c.Assert(err, jc.ErrorIsNil)
   397  
   398  	// Ensure that all the creation events have flowed through the system.
   399  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
   400  
   401  	assertChange, stop := s.assertSetupRelationStatusWatch(c, rel)
   402  	defer stop()
   403  
   404  	err = rel.Destroy()
   405  	c.Assert(err, jc.ErrorIsNil)
   406  	assertChange(life.Dead, false, "")
   407  }
   408  
   409  func (s *watcherSuite) setupOfferStatusWatch(
   410  	c *gc.C,
   411  ) (func(status status.Status, message string), func(), func()) {
   412  	// Create the offer connection details.
   413  	s.Factory.MakeUser(c, &factory.UserParams{Name: "fred"})
   414  	offers := state.NewApplicationOffers(s.State)
   415  	offer, err := offers.AddOffer(crossmodel.AddApplicationOfferArgs{
   416  		OfferName:       "hosted-mysql",
   417  		ApplicationName: "mysql",
   418  		Owner:           "admin",
   419  	})
   420  	c.Assert(err, jc.ErrorIsNil)
   421  
   422  	// Add the consume permission for the offer so the macaroon
   423  	// discharge can occur.
   424  	err = s.State.CreateOfferAccess(
   425  		names.NewApplicationOfferTag(offer.OfferUUID),
   426  		names.NewUserTag("fred"), permission.ConsumeAccess)
   427  	c.Assert(err, jc.ErrorIsNil)
   428  
   429  	// Create a macaroon for authorisation.
   430  	store, err := s.State.NewBakeryStorage()
   431  	c.Assert(err, jc.ErrorIsNil)
   432  	b := bakery.New(bakery.BakeryParams{
   433  		RootKeyStore: store,
   434  	})
   435  	c.Assert(err, jc.ErrorIsNil)
   436  	mac, err := b.Oven.NewMacaroon(
   437  		context.Background(),
   438  		bakery.LatestVersion,
   439  		[]checkers.Caveat{
   440  			checkers.DeclaredCaveat("source-model-uuid", s.State.ModelUUID()),
   441  			checkers.DeclaredCaveat("offer-uuid", offer.OfferUUID),
   442  			checkers.DeclaredCaveat("username", "fred"),
   443  		}, bakery.NoOp)
   444  	c.Assert(err, jc.ErrorIsNil)
   445  
   446  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
   447  	// Start watching for a relation change.
   448  	client := crossmodelrelations.NewClient(s.stateAPI)
   449  	w, err := client.WatchOfferStatus(params.OfferArg{
   450  		OfferUUID: offer.OfferUUID,
   451  		Macaroons: macaroon.Slice{mac.M()},
   452  	})
   453  	c.Assert(err, jc.ErrorIsNil)
   454  	stop := func() {
   455  		workertest.CleanKill(c, w)
   456  	}
   457  
   458  	assertNoChange := func() {
   459  		select {
   460  		case _, ok := <-w.Changes():
   461  			c.Fatalf("watcher sent unexpected change: (_, %v)", ok)
   462  		case <-time.After(coretesting.ShortWait):
   463  		}
   464  	}
   465  
   466  	assertChange := func(status status.Status, message string) {
   467  		select {
   468  		case changes, ok := <-w.Changes():
   469  			c.Check(ok, jc.IsTrue)
   470  			if status == "" {
   471  				c.Assert(changes, gc.HasLen, 0)
   472  				break
   473  			}
   474  			c.Assert(changes, gc.HasLen, 1)
   475  			c.Check(changes[0].Name, gc.Equals, "hosted-mysql")
   476  			c.Check(changes[0].Status.Status, gc.Equals, status)
   477  			c.Check(changes[0].Status.Message, gc.Equals, message)
   478  		case <-time.After(coretesting.LongWait):
   479  			c.Fatalf("watcher didn't emit an event")
   480  		}
   481  	}
   482  
   483  	// Initial event.
   484  	assertChange(status.Unknown, "")
   485  	return assertChange, assertNoChange, stop
   486  }
   487  
   488  func (s *watcherSuite) TestOfferStatusWatcher(c *gc.C) {
   489  	// Create a pair of services and a relation between them.
   490  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   491  
   492  	assertChange, assertNoChange, stop := s.setupOfferStatusWatch(c)
   493  	defer stop()
   494  
   495  	err := mysql.SetStatus(status.StatusInfo{Status: status.Unknown, Message: "another message"})
   496  	c.Assert(err, jc.ErrorIsNil)
   497  
   498  	assertChange(status.Unknown, "another message")
   499  
   500  	// Removing offer and application both trigger events.
   501  	offers := state.NewApplicationOffers(s.State)
   502  	err = offers.Remove("hosted-mysql", false)
   503  	c.Assert(err, jc.ErrorIsNil)
   504  	assertChange("terminated", "offer has been removed")
   505  	err = mysql.Destroy()
   506  	c.Assert(err, jc.ErrorIsNil)
   507  	assertChange("terminated", "offer has been removed")
   508  	assertNoChange()
   509  }
   510  
   511  func ptr[T any](v T) *T {
   512  	return &v
   513  }
   514  
   515  func (s *watcherSuite) setupSecretRotationWatcher(
   516  	c *gc.C,
   517  ) (*secrets.URI, func(corewatcher.SecretTriggerChange), func(), func()) {
   518  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{Name: "mysql"})
   519  	unit, password := s.Factory.MakeUnitReturningPassword(c, &factory.UnitParams{
   520  		Application: app,
   521  	})
   522  	store := state.NewSecrets(s.State)
   523  	uri := secrets.NewURI()
   524  	nexRotateTime := time.Now().Add(time.Hour)
   525  	_, err := store.CreateSecret(uri, state.CreateSecretParams{
   526  		Owner: unit.Tag(),
   527  		UpdateSecretParams: state.UpdateSecretParams{
   528  			LeaderToken:    &fakeToken{},
   529  			RotatePolicy:   ptr(secrets.RotateDaily),
   530  			NextRotateTime: ptr(nexRotateTime),
   531  			Data:           map[string]string{"foo": "bar"},
   532  		},
   533  	})
   534  	c.Assert(err, jc.ErrorIsNil)
   535  
   536  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
   537  
   538  	apiInfo := s.APIInfo(c)
   539  	apiInfo.Tag = unit.Tag()
   540  	apiInfo.Password = password
   541  	apiInfo.ModelTag = s.Model.ModelTag()
   542  
   543  	apiConn, err := api.Open(apiInfo, api.DialOpts{})
   544  	c.Assert(err, jc.ErrorIsNil)
   545  
   546  	client := secretsmanager.NewClient(apiConn)
   547  	w, err := client.WatchSecretsRotationChanges(unit.Tag())
   548  	if !c.Check(err, jc.ErrorIsNil) {
   549  		_ = apiConn.Close()
   550  		c.FailNow()
   551  	}
   552  	stop := func() {
   553  		workertest.CleanKill(c, w)
   554  		_ = apiConn.Close()
   555  	}
   556  
   557  	assertNoChange := func() {
   558  		select {
   559  		case _, ok := <-w.Changes():
   560  			c.Fatalf("watcher sent unexpected change: (_, %v)", ok)
   561  		case <-time.After(coretesting.ShortWait):
   562  		}
   563  	}
   564  
   565  	assertChange := func(change corewatcher.SecretTriggerChange) {
   566  		select {
   567  		case changes, ok := <-w.Changes():
   568  			c.Check(ok, jc.IsTrue)
   569  			c.Assert(changes, gc.HasLen, 1)
   570  			c.Assert(changes[0], jc.DeepEquals, change)
   571  		case <-time.After(coretesting.LongWait):
   572  			c.Fatalf("watcher didn't emit an event")
   573  		}
   574  	}
   575  
   576  	// Initial event.
   577  	assertChange(corewatcher.SecretTriggerChange{
   578  		URI:             uri,
   579  		NextTriggerTime: nexRotateTime.Round(time.Second).UTC(),
   580  	})
   581  	return uri, assertChange, assertNoChange, stop
   582  }
   583  
   584  type fakeToken struct{}
   585  
   586  func (t *fakeToken) Check() error {
   587  	return nil
   588  }
   589  
   590  func (s *watcherSuite) TestSecretsRotationWatcher(c *gc.C) {
   591  	uri, assertChange, assertNoChange, stop := s.setupSecretRotationWatcher(c)
   592  	defer stop()
   593  
   594  	store := state.NewSecrets(s.State)
   595  
   596  	nexRotateTime := time.Now().Add(24 * time.Hour).Round(time.Second)
   597  	_, err := store.UpdateSecret(uri, state.UpdateSecretParams{
   598  		LeaderToken:    &fakeToken{},
   599  		NextRotateTime: ptr(nexRotateTime),
   600  	})
   601  	c.Assert(err, jc.ErrorIsNil)
   602  
   603  	assertChange(corewatcher.SecretTriggerChange{
   604  		URI:             uri,
   605  		NextTriggerTime: nexRotateTime,
   606  	})
   607  	assertNoChange()
   608  
   609  	_, err = store.UpdateSecret(uri, state.UpdateSecretParams{
   610  		LeaderToken:  &fakeToken{},
   611  		RotatePolicy: ptr(secrets.RotateNever),
   612  	})
   613  	c.Assert(err, jc.ErrorIsNil)
   614  
   615  	assertChange(corewatcher.SecretTriggerChange{
   616  		URI:             uri,
   617  		NextTriggerTime: time.Time{},
   618  	})
   619  	assertNoChange()
   620  }
   621  
   622  func (s *watcherSuite) setupSecretExpiryWatcher(
   623  	c *gc.C,
   624  ) (*secrets.URI, func(corewatcher.SecretTriggerChange), func(), func()) {
   625  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{Name: "mysql"})
   626  	unit, password := s.Factory.MakeUnitReturningPassword(c, &factory.UnitParams{
   627  		Application: app,
   628  	})
   629  	store := state.NewSecrets(s.State)
   630  	uri := secrets.NewURI()
   631  	nexRotateTime := time.Now().Add(time.Hour)
   632  	_, err := store.CreateSecret(uri, state.CreateSecretParams{
   633  		Owner: unit.Tag(),
   634  		UpdateSecretParams: state.UpdateSecretParams{
   635  			LeaderToken:    &fakeToken{},
   636  			RotatePolicy:   ptr(secrets.RotateDaily),
   637  			NextRotateTime: ptr(nexRotateTime),
   638  			Data:           map[string]string{"foo": "bar"},
   639  		},
   640  	})
   641  	c.Assert(err, jc.ErrorIsNil)
   642  
   643  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
   644  
   645  	apiInfo := s.APIInfo(c)
   646  	apiInfo.Tag = unit.Tag()
   647  	apiInfo.Password = password
   648  	apiInfo.ModelTag = s.Model.ModelTag()
   649  
   650  	apiConn, err := api.Open(apiInfo, api.DialOpts{})
   651  	c.Assert(err, jc.ErrorIsNil)
   652  
   653  	client := secretsmanager.NewClient(apiConn)
   654  	w, err := client.WatchSecretsRotationChanges(unit.Tag())
   655  	if !c.Check(err, jc.ErrorIsNil) {
   656  		_ = apiConn.Close()
   657  		c.FailNow()
   658  	}
   659  	stop := func() {
   660  		workertest.CleanKill(c, w)
   661  		_ = apiConn.Close()
   662  	}
   663  
   664  	assertNoChange := func() {
   665  		select {
   666  		case _, ok := <-w.Changes():
   667  			c.Fatalf("watcher sent unexpected change: (_, %v)", ok)
   668  		case <-time.After(coretesting.ShortWait):
   669  		}
   670  	}
   671  
   672  	assertChange := func(change corewatcher.SecretTriggerChange) {
   673  		select {
   674  		case changes, ok := <-w.Changes():
   675  			c.Check(ok, jc.IsTrue)
   676  			c.Assert(changes, gc.HasLen, 1)
   677  			c.Assert(changes[0], jc.DeepEquals, change)
   678  		case <-time.After(coretesting.LongWait):
   679  			c.Fatalf("watcher didn't emit an event")
   680  		}
   681  	}
   682  
   683  	// Initial event.
   684  	assertChange(corewatcher.SecretTriggerChange{
   685  		URI:             uri,
   686  		NextTriggerTime: nexRotateTime.Round(time.Second).UTC(),
   687  	})
   688  	return uri, assertChange, assertNoChange, stop
   689  }
   690  
   691  func (s *watcherSuite) TestSecretsExpiryWatcher(c *gc.C) {
   692  	uri, assertChange, assertNoChange, stop := s.setupSecretExpiryWatcher(c)
   693  	defer stop()
   694  
   695  	store := state.NewSecrets(s.State)
   696  
   697  	nexRotateTime := time.Now().Add(24 * time.Hour).Round(time.Second)
   698  	_, err := store.UpdateSecret(uri, state.UpdateSecretParams{
   699  		LeaderToken:    &fakeToken{},
   700  		NextRotateTime: ptr(nexRotateTime),
   701  	})
   702  	c.Assert(err, jc.ErrorIsNil)
   703  
   704  	assertChange(corewatcher.SecretTriggerChange{
   705  		URI:             uri,
   706  		NextTriggerTime: nexRotateTime,
   707  	})
   708  	assertNoChange()
   709  
   710  	_, err = store.UpdateSecret(uri, state.UpdateSecretParams{
   711  		LeaderToken:  &fakeToken{},
   712  		RotatePolicy: ptr(secrets.RotateNever),
   713  	})
   714  	c.Assert(err, jc.ErrorIsNil)
   715  
   716  	assertChange(corewatcher.SecretTriggerChange{
   717  		URI:             uri,
   718  		NextTriggerTime: time.Time{},
   719  	})
   720  	assertNoChange()
   721  }
   722  
   723  func (s *watcherSuite) setupSecretsRevisionWatcher(
   724  	c *gc.C,
   725  ) (*secrets.URI, func(uri *secrets.URI, rev int), func(), func()) {
   726  	// Set up the offer.
   727  	app := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   728  	unit, password := s.Factory.MakeUnitReturningPassword(c, &factory.UnitParams{
   729  		Application: app,
   730  	})
   731  	s.Factory.MakeUser(c, &factory.UserParams{Name: "fred"})
   732  	offers := state.NewApplicationOffers(s.State)
   733  	offer, err := offers.AddOffer(crossmodel.AddApplicationOfferArgs{
   734  		OfferName:       "hosted-mysql",
   735  		ApplicationName: "mysql",
   736  		Owner:           "admin",
   737  	})
   738  	c.Assert(err, jc.ErrorIsNil)
   739  
   740  	// Add the consume permission for the offer so the macaroon
   741  	// discharge can occur.
   742  	err = s.State.CreateOfferAccess(
   743  		names.NewApplicationOfferTag(offer.OfferUUID),
   744  		names.NewUserTag("fred"), permission.ConsumeAccess)
   745  	c.Assert(err, jc.ErrorIsNil)
   746  
   747  	// Create a macaroon for authorisation.
   748  	bStore, err := s.State.NewBakeryStorage()
   749  	c.Assert(err, jc.ErrorIsNil)
   750  	b := bakery.New(bakery.BakeryParams{
   751  		RootKeyStore: bStore,
   752  	})
   753  	c.Assert(err, jc.ErrorIsNil)
   754  	mac, err := b.Oven.NewMacaroon(
   755  		context.Background(),
   756  		bakery.LatestVersion,
   757  		[]checkers.Caveat{
   758  			checkers.DeclaredCaveat("source-model-uuid", s.State.ModelUUID()),
   759  			checkers.DeclaredCaveat("offer-uuid", offer.OfferUUID),
   760  			checkers.DeclaredCaveat("username", "fred"),
   761  		}, bakery.Op{
   762  			Entity: "offer-uuid",
   763  			Action: "consume",
   764  		})
   765  	c.Assert(err, jc.ErrorIsNil)
   766  
   767  	remoteApp, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{
   768  		Name: "foo", OfferUUID: offer.OfferUUID, URL: "me/model.foo", SourceModel: s.Model.ModelTag()})
   769  	c.Assert(err, jc.ErrorIsNil)
   770  	remoteRel, err := s.State.AddRelation(
   771  		state.Endpoint{"mysql", charm.Relation{Name: "server", Interface: "mysql", Role: charm.RoleProvider, Scope: charm.ScopeGlobal}},
   772  		state.Endpoint{"foo", charm.Relation{Name: "db", Interface: "mysql", Role: charm.RoleRequirer, Scope: charm.ScopeGlobal}},
   773  	)
   774  	c.Assert(err, jc.ErrorIsNil)
   775  
   776  	_, err = s.State.AddOfferConnection(state.AddOfferConnectionParams{
   777  		SourceModelUUID: utils.MustNewUUID().String(),
   778  		OfferUUID:       offer.OfferUUID,
   779  		Username:        "fred",
   780  		RelationId:      remoteRel.Id(),
   781  		RelationKey:     remoteRel.Tag().Id(),
   782  	})
   783  	c.Assert(err, jc.ErrorIsNil)
   784  
   785  	// Export the remote entities so they can be found with a token.
   786  	re := s.State.RemoteEntities()
   787  	appToken, err := re.ExportLocalEntity(remoteApp.Tag())
   788  	c.Assert(err, jc.ErrorIsNil)
   789  	relToken, err := re.ExportLocalEntity(remoteRel.Tag())
   790  	c.Assert(err, jc.ErrorIsNil)
   791  
   792  	// Create a secret to watch.
   793  	store := state.NewSecrets(s.State)
   794  	uri := secrets.NewURI()
   795  	_, err = store.CreateSecret(uri, state.CreateSecretParams{
   796  		Owner: unit.Tag(),
   797  		UpdateSecretParams: state.UpdateSecretParams{
   798  			LeaderToken:  &fakeToken{},
   799  			RotatePolicy: ptr(secrets.RotateDaily),
   800  			Data:         map[string]string{"foo": "bar"},
   801  		},
   802  	})
   803  	c.Assert(err, jc.ErrorIsNil)
   804  
   805  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
   806  
   807  	apiInfo := s.APIInfo(c)
   808  	apiInfo.Tag = unit.Tag()
   809  	apiInfo.Password = password
   810  	apiInfo.ModelTag = s.Model.ModelTag()
   811  
   812  	apiConn, err := api.Open(apiInfo, api.DialOpts{})
   813  	c.Assert(err, jc.ErrorIsNil)
   814  
   815  	client := crossmodelrelations.NewClient(apiConn)
   816  	w, err := client.WatchConsumedSecretsChanges(appToken, relToken, mac.M())
   817  	if !c.Check(err, jc.ErrorIsNil) {
   818  		_ = apiConn.Close()
   819  		c.FailNow()
   820  	}
   821  	stop := func() {
   822  		workertest.CleanKill(c, w)
   823  		_ = apiConn.Close()
   824  	}
   825  
   826  	assertNoChange := func() {
   827  		select {
   828  		case _, ok := <-w.Changes():
   829  			c.Fatalf("watcher sent unexpected change: (_, %v)", ok)
   830  		case <-time.After(coretesting.ShortWait):
   831  		}
   832  	}
   833  
   834  	assertChange := func(uri *secrets.URI, rev int) {
   835  		select {
   836  		case changes, ok := <-w.Changes():
   837  			c.Check(ok, jc.IsTrue)
   838  			if uri == nil {
   839  				c.Assert(changes, gc.HasLen, 0)
   840  				break
   841  			}
   842  			c.Assert(changes, gc.HasLen, 1)
   843  			c.Assert(changes[0].URI.String(), gc.Equals, uri.String())
   844  			c.Assert(changes[0].Revision, gc.Equals, rev)
   845  		case <-time.After(coretesting.LongWait):
   846  			c.Fatalf("watcher didn't emit an event")
   847  		}
   848  	}
   849  	return uri, assertChange, assertNoChange, stop
   850  }
   851  
   852  func (s *watcherSuite) TestCrossModelSecretsRevisionWatcher(c *gc.C) {
   853  	uri, assertChange, assertNoChange, stop := s.setupSecretsRevisionWatcher(c)
   854  	defer stop()
   855  
   856  	store := state.NewSecrets(s.State)
   857  
   858  	// Initial event - no changes since we're at rev 1 still.
   859  	assertChange(nil, 0)
   860  
   861  	err := s.State.SaveSecretRemoteConsumer(uri, names.NewUnitTag("foo/0"), &secrets.SecretConsumerMetadata{
   862  		CurrentRevision: 1,
   863  		LatestRevision:  1,
   864  	})
   865  	c.Assert(err, jc.ErrorIsNil)
   866  	assertNoChange()
   867  
   868  	_, err = store.UpdateSecret(uri, state.UpdateSecretParams{
   869  		LeaderToken: &fakeToken{},
   870  		Data:        secrets.SecretData{"foo": "bar2"},
   871  	})
   872  	c.Assert(err, jc.ErrorIsNil)
   873  
   874  	assertChange(uri, 2)
   875  	assertNoChange()
   876  }
   877  
   878  type migrationSuite struct {
   879  	testing.JujuConnSuite
   880  }
   881  
   882  var _ = gc.Suite(&migrationSuite{})
   883  
   884  func (s *migrationSuite) TestMigrationStatusWatcher(c *gc.C) {
   885  	const nonce = "noncey"
   886  
   887  	// Create a model to migrate.
   888  	hostedState := s.Factory.MakeModel(c, &factory.ModelParams{})
   889  	defer hostedState.Close()
   890  	hostedFactory := factory.NewFactory(hostedState, s.StatePool)
   891  
   892  	// Create a machine in the hosted model to connect as.
   893  	m, password := hostedFactory.MakeMachineReturningPassword(c, &factory.MachineParams{
   894  		Nonce: nonce,
   895  	})
   896  
   897  	// Connect as the machine to watch for migration status.
   898  	apiInfo := s.APIInfo(c)
   899  	apiInfo.Tag = m.Tag()
   900  	apiInfo.Password = password
   901  
   902  	hostedModel, err := hostedState.Model()
   903  	c.Assert(err, jc.ErrorIsNil)
   904  
   905  	apiInfo.ModelTag = hostedModel.ModelTag()
   906  	apiInfo.Nonce = nonce
   907  
   908  	apiConn, err := api.Open(apiInfo, api.DialOpts{})
   909  	c.Assert(err, jc.ErrorIsNil)
   910  	defer apiConn.Close()
   911  
   912  	// Start watching for a migration.
   913  	client := migrationminion.NewClient(apiConn)
   914  	w, err := client.Watch()
   915  	c.Assert(err, jc.ErrorIsNil)
   916  	defer func() {
   917  		c.Assert(worker.Stop(w), jc.ErrorIsNil)
   918  	}()
   919  
   920  	assertNoChange := func() {
   921  		select {
   922  		case _, ok := <-w.Changes():
   923  			c.Fatalf("watcher sent unexpected change: (_, %v)", ok)
   924  		case <-time.After(coretesting.ShortWait):
   925  		}
   926  	}
   927  
   928  	assertChange := func(id string, phase migration.Phase) {
   929  		select {
   930  		case status, ok := <-w.Changes():
   931  			c.Assert(ok, jc.IsTrue)
   932  			c.Check(status.MigrationId, gc.Equals, id)
   933  			c.Check(status.Phase, gc.Equals, phase)
   934  		case <-time.After(coretesting.LongWait):
   935  			c.Fatalf("watcher didn't emit an event")
   936  		}
   937  		assertNoChange()
   938  	}
   939  
   940  	// Initial event with no migration in progress.
   941  	assertChange("", migration.NONE)
   942  
   943  	// Now create a migration, should trigger watcher.
   944  	spec := state.MigrationSpec{
   945  		InitiatedBy: names.NewUserTag("someone"),
   946  		TargetInfo: migration.TargetInfo{
   947  			ControllerTag: names.NewControllerTag(utils.MustNewUUID().String()),
   948  			Addrs:         []string{"1.2.3.4:5"},
   949  			CACert:        "cert",
   950  			AuthTag:       names.NewUserTag("dog"),
   951  			Password:      "sekret",
   952  		},
   953  	}
   954  	mig, err := hostedState.CreateMigration(spec)
   955  	c.Assert(err, jc.ErrorIsNil)
   956  	assertChange(mig.Id(), migration.QUIESCE)
   957  
   958  	// Now abort the migration, this should be reported too.
   959  	c.Assert(mig.SetPhase(migration.ABORT), jc.ErrorIsNil)
   960  	assertChange(mig.Id(), migration.ABORT)
   961  	c.Assert(mig.SetPhase(migration.ABORTDONE), jc.ErrorIsNil)
   962  	assertChange(mig.Id(), migration.ABORTDONE)
   963  
   964  	// Start a new migration, this should also trigger.
   965  	mig2, err := hostedState.CreateMigration(spec)
   966  	c.Assert(err, jc.ErrorIsNil)
   967  	assertChange(mig2.Id(), migration.QUIESCE)
   968  }