github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/remoterelations/remoterelations_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package remoterelations_test
     5  
     6  import (
     7  	"reflect"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	jujutesting "github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/juju/names.v2"
    16  	"gopkg.in/juju/worker.v1"
    17  	"gopkg.in/juju/worker.v1/workertest"
    18  	"gopkg.in/macaroon.v2-unstable"
    19  
    20  	"github.com/juju/juju/api"
    21  	apitesting "github.com/juju/juju/api/testing"
    22  	"github.com/juju/juju/apiserver/common"
    23  	"github.com/juju/juju/apiserver/params"
    24  	apiservertesting "github.com/juju/juju/apiserver/testing"
    25  	"github.com/juju/juju/core/life"
    26  	"github.com/juju/juju/core/status"
    27  	"github.com/juju/juju/core/watcher"
    28  	coretesting "github.com/juju/juju/testing"
    29  	"github.com/juju/juju/worker/remoterelations"
    30  )
    31  
    32  var _ = gc.Suite(&remoteRelationsSuite{})
    33  
    34  type remoteRelationsSuite struct {
    35  	coretesting.BaseSuite
    36  
    37  	resources             *common.Resources
    38  	authorizer            *apiservertesting.FakeAuthorizer
    39  	relationsFacade       *mockRelationsFacade
    40  	remoteRelationsFacade *mockRemoteRelationsFacade
    41  	config                remoterelations.Config
    42  	stub                  *jujutesting.Stub
    43  }
    44  
    45  func (s *remoteRelationsSuite) SetUpTest(c *gc.C) {
    46  	s.BaseSuite.SetUpTest(c)
    47  
    48  	s.stub = new(jujutesting.Stub)
    49  	s.relationsFacade = newMockRelationsFacade(s.stub)
    50  	s.remoteRelationsFacade = newMockRemoteRelationsFacade(s.stub)
    51  	s.config = remoterelations.Config{
    52  		ModelUUID:       "local-model-uuid",
    53  		RelationsFacade: s.relationsFacade,
    54  		NewRemoteModelFacadeFunc: func(*api.Info) (remoterelations.RemoteModelRelationsFacadeCloser, error) {
    55  			return s.remoteRelationsFacade, nil
    56  		},
    57  		Clock: testclock.NewClock(time.Time{}),
    58  	}
    59  }
    60  
    61  func (s *remoteRelationsSuite) waitForWorkerStubCalls(c *gc.C, expected []jujutesting.StubCall) {
    62  	waitForStubCalls(c, s.stub, expected)
    63  }
    64  
    65  func waitForStubCalls(c *gc.C, stub *jujutesting.Stub, expected []jujutesting.StubCall) {
    66  	var calls []jujutesting.StubCall
    67  	for a := coretesting.LongAttempt.Start(); a.Next(); {
    68  		calls = stub.Calls()
    69  		if reflect.DeepEqual(calls, expected) {
    70  			return
    71  		}
    72  	}
    73  	c.Fatalf("failed to see expected calls.\nexpected: %v\nobserved: %v", expected, calls)
    74  }
    75  
    76  func (s *remoteRelationsSuite) assertRemoteApplicationWorkers(c *gc.C) worker.Worker {
    77  	// Checks that the main worker loop responds to remote application events
    78  	// by starting relevant relation watchers.
    79  	s.relationsFacade.remoteApplications["db2"] = newMockRemoteApplication("db2", "db2url")
    80  	s.relationsFacade.remoteApplications["mysql"] = newMockRemoteApplication("mysql", "mysqlurl")
    81  	s.relationsFacade.controllerInfo["remote-model-uuid"] = &api.Info{
    82  		Addrs: []string{"1.2.3.4:1234"}, CACert: coretesting.CACert}
    83  
    84  	w, err := remoterelations.New(s.config)
    85  	c.Assert(err, jc.ErrorIsNil)
    86  	expected := []jujutesting.StubCall{
    87  		{"WatchRemoteApplications", nil},
    88  	}
    89  	s.waitForWorkerStubCalls(c, expected)
    90  	s.stub.ResetCalls()
    91  
    92  	mac, err := apitesting.NewMacaroon("test")
    93  	c.Assert(err, jc.ErrorIsNil)
    94  	s.relationsFacade.remoteApplicationsWatcher.changes <- []string{"db2"}
    95  	expected = []jujutesting.StubCall{
    96  		{"RemoteApplications", []interface{}{[]string{"db2"}}},
    97  		{"WatchRemoteApplicationRelations", []interface{}{"db2"}},
    98  		{"ControllerAPIInfoForModel", []interface{}{"remote-model-uuid"}},
    99  		{"WatchOfferStatus", []interface{}{"offer-db2-uuid", macaroon.Slice{mac}}},
   100  	}
   101  	s.waitForWorkerStubCalls(c, expected)
   102  	s.stub.ResetCalls()
   103  
   104  	s.relationsFacade.remoteApplicationsWatcher.changes <- []string{"mysql"}
   105  	expected = []jujutesting.StubCall{
   106  		{"RemoteApplications", []interface{}{[]string{"mysql"}}},
   107  		{"WatchRemoteApplicationRelations", []interface{}{"mysql"}},
   108  		{"ControllerAPIInfoForModel", []interface{}{"remote-model-uuid"}},
   109  		{"WatchOfferStatus", []interface{}{"offer-mysql-uuid", macaroon.Slice{mac}}},
   110  	}
   111  	s.waitForWorkerStubCalls(c, expected)
   112  	s.stub.ResetCalls()
   113  
   114  	applicationNames := []string{"db2", "mysql"}
   115  	for _, app := range applicationNames {
   116  		w, ok := s.relationsFacade.remoteApplicationRelationsWatcher(app)
   117  		c.Check(ok, jc.IsTrue)
   118  		waitForStubCalls(c, &w.Stub, []jujutesting.StubCall{
   119  			{"Changes", nil},
   120  		})
   121  	}
   122  	return w
   123  }
   124  
   125  func (s *remoteRelationsSuite) TestRemoteApplicationWorkers(c *gc.C) {
   126  	w := s.assertRemoteApplicationWorkers(c)
   127  	workertest.CleanKill(c, w)
   128  
   129  	// Check that relation watchers are stopped with the worker.
   130  	applicationNames := []string{"db2", "mysql"}
   131  	for _, app := range applicationNames {
   132  		w, ok := s.relationsFacade.remoteApplicationRelationsWatcher(app)
   133  		c.Check(ok, jc.IsTrue)
   134  		c.Check(w.killed(), jc.IsTrue)
   135  	}
   136  }
   137  
   138  func (s *remoteRelationsSuite) TestRemoteApplicationRemoved(c *gc.C) {
   139  	// Checks that when a remote application is removed, the relation
   140  	// worker is killed.
   141  	w := s.assertRemoteApplicationWorkers(c)
   142  	defer workertest.CleanKill(c, w)
   143  	s.stub.ResetCalls()
   144  
   145  	relWatcher, _ := s.relationsFacade.removeApplication("mysql")
   146  	s.relationsFacade.remoteApplicationsWatcher.changes <- []string{"mysql"}
   147  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   148  		_, ok := s.relationsFacade.remoteApplicationRelationsWatcher("mysql")
   149  		if !ok {
   150  			break
   151  		}
   152  	}
   153  	c.Check(relWatcher.killed(), jc.IsTrue)
   154  	expected := []jujutesting.StubCall{
   155  		{"RemoteApplications", []interface{}{[]string{"mysql"}}},
   156  		{"Close", nil},
   157  	}
   158  	s.waitForWorkerStubCalls(c, expected)
   159  }
   160  
   161  func (s *remoteRelationsSuite) TestRemoteNotFoundTerminatesOnWatching(c *gc.C) {
   162  	s.relationsFacade.remoteApplications["db2"] = newMockRemoteApplication("db2", "db2url")
   163  	s.relationsFacade.remoteApplications["mysql"] = newMockRemoteApplication("mysql", "mysqlurl")
   164  	s.relationsFacade.controllerInfo["remote-model-uuid"] = &api.Info{
   165  		Addrs: []string{"1.2.3.4:1234"}, CACert: coretesting.CACert}
   166  
   167  	w, err := remoterelations.New(s.config)
   168  	c.Assert(err, jc.ErrorIsNil)
   169  	defer workertest.CleanKill(c, w)
   170  
   171  	expected := []jujutesting.StubCall{
   172  		{"WatchRemoteApplications", nil},
   173  	}
   174  	s.waitForWorkerStubCalls(c, expected)
   175  	s.stub.ResetCalls()
   176  
   177  	s.stub.SetErrors(nil, nil, nil, params.Error{Code: params.CodeNotFound})
   178  
   179  	mac, err := apitesting.NewMacaroon("test")
   180  	c.Assert(err, jc.ErrorIsNil)
   181  	s.relationsFacade.remoteApplicationsWatcher.changes <- []string{"db2"}
   182  	expected = []jujutesting.StubCall{
   183  		{"RemoteApplications", []interface{}{[]string{"db2"}}},
   184  		{"WatchRemoteApplicationRelations", []interface{}{"db2"}},
   185  		{"ControllerAPIInfoForModel", []interface{}{"remote-model-uuid"}},
   186  		{"WatchOfferStatus", []interface{}{"offer-db2-uuid", macaroon.Slice{mac}}},
   187  		{"SetRemoteApplicationStatus", []interface{}{"db2", "terminated", "offer has been removed"}},
   188  		{"Close", nil},
   189  	}
   190  	s.waitForWorkerStubCalls(c, expected)
   191  }
   192  
   193  func (s *remoteRelationsSuite) TestOfferStatusChange(c *gc.C) {
   194  	w := s.assertRemoteApplicationWorkers(c)
   195  	defer workertest.CleanKill(c, w)
   196  	s.stub.ResetCalls()
   197  
   198  	statusWatcher := s.remoteRelationsFacade.offersStatusWatchers["offer-mysql-uuid"]
   199  	statusWatcher.changes <- []watcher.OfferStatusChange{{
   200  		Name: "mysql",
   201  		Status: status.StatusInfo{
   202  			Status:  status.Active,
   203  			Message: "started",
   204  		},
   205  	}}
   206  
   207  	expected := []jujutesting.StubCall{
   208  		{"SetRemoteApplicationStatus", []interface{}{"mysql", "active", "started"}},
   209  	}
   210  	s.waitForWorkerStubCalls(c, expected)
   211  }
   212  
   213  func (s *remoteRelationsSuite) TestRemoteNotFoundTerminatesOnChange(c *gc.C) {
   214  	s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123)
   215  	w := s.assertRemoteApplicationWorkers(c)
   216  	defer workertest.CleanKill(c, w)
   217  
   218  	s.stub.ResetCalls()
   219  	s.stub.SetErrors(nil, nil, params.Error{Code: params.CodeNotFound})
   220  
   221  	s.relationsFacade.relationsEndpoints["db2:db django:db"] = &relationEndpointInfo{
   222  		localApplicationName: "django",
   223  		localEndpoint: params.RemoteEndpoint{
   224  			Name:      "db2",
   225  			Role:      "requires",
   226  			Interface: "db2",
   227  		},
   228  		remoteEndpointName: "data",
   229  	}
   230  
   231  	relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2")
   232  	relWatcher.changes <- []string{"db2:db django:db"}
   233  
   234  	mac, err := apitesting.NewMacaroon("test")
   235  	c.Assert(err, jc.ErrorIsNil)
   236  	relTag := names.NewRelationTag("db2:db django:db")
   237  	expected := []jujutesting.StubCall{
   238  		{"Relations", []interface{}{[]string{"db2:db django:db"}}},
   239  		{"ExportEntities", []interface{}{
   240  			[]names.Tag{names.NewApplicationTag("django"), relTag}}},
   241  		{"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{
   242  			ApplicationToken: "token-django",
   243  			SourceModelTag:   "model-local-model-uuid",
   244  			RelationToken:    "token-db2:db django:db",
   245  			RemoteEndpoint: params.RemoteEndpoint{
   246  				Name:      "db2",
   247  				Role:      "requires",
   248  				Interface: "db2",
   249  			},
   250  			OfferUUID:         "offer-db2-uuid",
   251  			LocalEndpointName: "data",
   252  			Macaroons:         macaroon.Slice{mac},
   253  		}}}},
   254  		{"SetRemoteApplicationStatus", []interface{}{"db2", "terminated", "offer has been removed"}},
   255  		{"Close", nil},
   256  	}
   257  	s.waitForWorkerStubCalls(c, expected)
   258  }
   259  
   260  func (s *remoteRelationsSuite) assertRemoteRelationsWorkers(c *gc.C) worker.Worker {
   261  	s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123)
   262  	w := s.assertRemoteApplicationWorkers(c)
   263  	s.stub.ResetCalls()
   264  
   265  	s.relationsFacade.relationsEndpoints["db2:db django:db"] = &relationEndpointInfo{
   266  		localApplicationName: "django",
   267  		localEndpoint: params.RemoteEndpoint{
   268  			Name:      "db2",
   269  			Role:      "requires",
   270  			Interface: "db2",
   271  		},
   272  		remoteEndpointName: "data",
   273  	}
   274  
   275  	relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2")
   276  	relWatcher.changes <- []string{"db2:db django:db"}
   277  
   278  	mac, err := apitesting.NewMacaroon("test")
   279  	c.Assert(err, jc.ErrorIsNil)
   280  	apiMac, err := apitesting.NewMacaroon("apimac")
   281  	c.Assert(err, jc.ErrorIsNil)
   282  	relTag := names.NewRelationTag("db2:db django:db")
   283  	expected := []jujutesting.StubCall{
   284  		{"Relations", []interface{}{[]string{"db2:db django:db"}}},
   285  		{"ExportEntities", []interface{}{
   286  			[]names.Tag{names.NewApplicationTag("django"), relTag}}},
   287  		{"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{
   288  			ApplicationToken: "token-django",
   289  			SourceModelTag:   "model-local-model-uuid",
   290  			RelationToken:    "token-db2:db django:db",
   291  			RemoteEndpoint: params.RemoteEndpoint{
   292  				Name:      "db2",
   293  				Role:      "requires",
   294  				Interface: "db2",
   295  			},
   296  			OfferUUID:         "offer-db2-uuid",
   297  			LocalEndpointName: "data",
   298  			Macaroons:         macaroon.Slice{mac},
   299  		}}}},
   300  		{"SaveMacaroon", []interface{}{relTag, apiMac}},
   301  		{"ImportRemoteEntity", []interface{}{names.NewApplicationTag("db2"), "token-offer-db2-uuid"}},
   302  		{"WatchRelationSuspendedStatus", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}},
   303  		{"WatchLocalRelationUnits", []interface{}{"db2:db django:db"}},
   304  		{"WatchRelationUnits", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}},
   305  	}
   306  	s.waitForWorkerStubCalls(c, expected)
   307  
   308  	unitWatcher, ok := s.relationsFacade.relationsUnitsWatcher("db2:db django:db")
   309  	c.Check(ok, jc.IsTrue)
   310  	waitForStubCalls(c, &unitWatcher.Stub, []jujutesting.StubCall{
   311  		{"Changes", nil},
   312  	})
   313  	unitWatcher, ok = s.remoteRelationsFacade.relationsUnitsWatcher("token-db2:db django:db")
   314  	c.Check(ok, jc.IsTrue)
   315  	waitForStubCalls(c, &unitWatcher.Stub, []jujutesting.StubCall{
   316  		{"Changes", nil},
   317  	})
   318  	relationStatusWatcher, ok := s.remoteRelationsFacade.relationsStatusWatcher("token-db2:db django:db")
   319  	c.Check(ok, jc.IsTrue)
   320  	waitForStubCalls(c, &relationStatusWatcher.Stub, []jujutesting.StubCall{
   321  		{"Changes", nil},
   322  	})
   323  	return w
   324  }
   325  
   326  func (s *remoteRelationsSuite) TestRemoteRelationsWorkers(c *gc.C) {
   327  	w := s.assertRemoteRelationsWorkers(c)
   328  	workertest.CleanKill(c, w)
   329  
   330  	// Check that relation unit watchers are stopped with the worker.
   331  	relWatcher, ok := s.relationsFacade.relationsUnitsWatchers["db2:db django:db"]
   332  	c.Check(ok, jc.IsTrue)
   333  	c.Check(relWatcher.killed(), jc.IsTrue)
   334  
   335  	relWatcher, ok = s.remoteRelationsFacade.relationsUnitsWatchers["token-db2:db django:db"]
   336  	c.Check(ok, jc.IsTrue)
   337  	c.Check(relWatcher.killed(), jc.IsTrue)
   338  }
   339  
   340  func (s *remoteRelationsSuite) TestRemoteRelationsRevoked(c *gc.C) {
   341  	// The consume permission is revoked after an offer is consumed.
   342  	// Subsequent api calls against that offer will fail and record an
   343  	// error in the local model.
   344  	s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123)
   345  	w := s.assertRemoteApplicationWorkers(c)
   346  	defer workertest.CleanKill(c, w)
   347  	s.stub.ResetCalls()
   348  	s.stub.SetErrors(nil, nil, &params.Error{
   349  		Code:    params.CodeDischargeRequired,
   350  		Message: "message",
   351  	})
   352  
   353  	s.relationsFacade.relationsEndpoints["db2:db django:db"] = &relationEndpointInfo{
   354  		localApplicationName: "django",
   355  		localEndpoint: params.RemoteEndpoint{
   356  			Name:      "db2",
   357  			Role:      "requires",
   358  			Interface: "db2",
   359  		},
   360  		remoteEndpointName: "data",
   361  	}
   362  
   363  	relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2")
   364  	relWatcher.changes <- []string{"db2:db django:db"}
   365  
   366  	mac, err := apitesting.NewMacaroon("test")
   367  	c.Assert(err, jc.ErrorIsNil)
   368  	relTag := names.NewRelationTag("db2:db django:db")
   369  	expected := []jujutesting.StubCall{
   370  		{"Relations", []interface{}{[]string{"db2:db django:db"}}},
   371  		{"ExportEntities", []interface{}{
   372  			[]names.Tag{names.NewApplicationTag("django"), relTag}}},
   373  		{"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{
   374  			ApplicationToken: "token-django",
   375  			SourceModelTag:   "model-local-model-uuid",
   376  			RelationToken:    "token-db2:db django:db",
   377  			RemoteEndpoint: params.RemoteEndpoint{
   378  				Name:      "db2",
   379  				Role:      "requires",
   380  				Interface: "db2",
   381  			},
   382  			OfferUUID:         "offer-db2-uuid",
   383  			LocalEndpointName: "data",
   384  			Macaroons:         macaroon.Slice{mac},
   385  		}}}},
   386  		{"SetRemoteApplicationStatus", []interface{}{"db2", "error", "message"}},
   387  		{"Close", nil},
   388  	}
   389  	s.waitForWorkerStubCalls(c, expected)
   390  }
   391  
   392  func (s *remoteRelationsSuite) TestRemoteRelationsDying(c *gc.C) {
   393  	// Checks that when a remote relation dies, the relation units
   394  	// workers are killed.
   395  	w := s.assertRemoteRelationsWorkers(c)
   396  	defer workertest.CleanKill(c, w)
   397  	s.stub.ResetCalls()
   398  
   399  	unitsWatcher, _ := s.relationsFacade.updateRelationLife("db2:db django:db", params.Dying)
   400  	relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2")
   401  	relWatcher.changes <- []string{"db2:db django:db"}
   402  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   403  		_, ok := s.relationsFacade.relationsUnitsWatcher("db2:db django:db")
   404  		if ok {
   405  			continue
   406  		}
   407  		_, ok = s.remoteRelationsFacade.relationsUnitsWatcher("token-db2:db django:db")
   408  		if !ok {
   409  			break
   410  		}
   411  		_, ok = s.remoteRelationsFacade.relationsStatusWatcher("token-db2:db django:db")
   412  		if !ok {
   413  			break
   414  		}
   415  	}
   416  	// We keep the relation units watcher alive when the relation
   417  	// goes to Dying; they're only stopped when the relation is
   418  	// finally removed.
   419  	c.Assert(unitsWatcher.killed(), jc.IsFalse)
   420  	apiMac, err := apitesting.NewMacaroon("apimac")
   421  	c.Assert(err, jc.ErrorIsNil)
   422  	mac, err := apitesting.NewMacaroon("test")
   423  	c.Assert(err, jc.ErrorIsNil)
   424  	relTag := names.NewRelationTag("db2:db django:db")
   425  	expected := []jujutesting.StubCall{
   426  		{"Relations", []interface{}{[]string{"db2:db django:db"}}},
   427  		{"ExportEntities", []interface{}{
   428  			[]names.Tag{names.NewApplicationTag("django"), relTag}}},
   429  		{"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{
   430  			ApplicationToken: "token-django",
   431  			SourceModelTag:   "model-local-model-uuid",
   432  			RelationToken:    "token-db2:db django:db",
   433  			RemoteEndpoint: params.RemoteEndpoint{
   434  				Name:      "db2",
   435  				Role:      "requires",
   436  				Interface: "db2",
   437  			},
   438  			OfferUUID:         "offer-db2-uuid",
   439  			LocalEndpointName: "data",
   440  			Macaroons:         macaroon.Slice{mac},
   441  		}}}},
   442  		{"SaveMacaroon", []interface{}{relTag, apiMac}},
   443  		{"ImportRemoteEntity", []interface{}{names.NewApplicationTag("db2"), "token-offer-db2-uuid"}},
   444  		{"PublishRelationChange", []interface{}{
   445  			params.RemoteRelationChangeEvent{
   446  				Life:             params.Dying,
   447  				ApplicationToken: "token-django",
   448  				RelationToken:    "token-db2:db django:db",
   449  				Macaroons:        macaroon.Slice{apiMac},
   450  			},
   451  		}},
   452  	}
   453  	s.waitForWorkerStubCalls(c, expected)
   454  }
   455  
   456  func (s *remoteRelationsSuite) TestLocalRelationsRemoved(c *gc.C) {
   457  	// Checks that when a remote relation goes away, the relation units
   458  	// worker is killed.
   459  	w := s.assertRemoteRelationsWorkers(c)
   460  	defer workertest.CleanKill(c, w)
   461  	s.stub.ResetCalls()
   462  
   463  	unitsWatcher, _ := s.relationsFacade.removeRelation("db2:db django:db")
   464  	relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2")
   465  	relWatcher.changes <- []string{"db2:db django:db"}
   466  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   467  		_, ok := s.relationsFacade.relationsUnitsWatcher("db2:db django:db")
   468  		if !ok {
   469  			break
   470  		}
   471  	}
   472  	c.Assert(unitsWatcher.killed(), jc.IsTrue)
   473  	expected := []jujutesting.StubCall{
   474  		{"Relations", []interface{}{[]string{"db2:db django:db"}}},
   475  	}
   476  	s.waitForWorkerStubCalls(c, expected)
   477  }
   478  
   479  func (s *remoteRelationsSuite) TestLocalRelationsChangedNotifies(c *gc.C) {
   480  	w := s.assertRemoteRelationsWorkers(c)
   481  	defer workertest.CleanKill(c, w)
   482  	s.stub.ResetCalls()
   483  
   484  	unitsWatcher, _ := s.relationsFacade.relationsUnitsWatcher("db2:db django:db")
   485  	unitsWatcher.changes <- watcher.RelationUnitsChange{
   486  		Changed:  map[string]watcher.UnitSettings{"unit/1": {Version: 2}},
   487  		Departed: []string{"unit/2"},
   488  	}
   489  
   490  	mac, err := apitesting.NewMacaroon("apimac")
   491  	c.Assert(err, jc.ErrorIsNil)
   492  	expected := []jujutesting.StubCall{
   493  		{"RelationUnitSettings", []interface{}{
   494  			[]params.RelationUnit{{
   495  				Relation: "relation-db2.db#django.db",
   496  				Unit:     "unit-unit-1"}}}},
   497  		{"PublishRelationChange", []interface{}{
   498  			params.RemoteRelationChangeEvent{
   499  				ApplicationToken: "token-django",
   500  				RelationToken:    "token-db2:db django:db",
   501  				ChangedUnits: []params.RemoteRelationUnitChange{{
   502  					UnitId:   1,
   503  					Settings: map[string]interface{}{"foo": "bar"},
   504  				}},
   505  				DepartedUnits: []int{2},
   506  				Macaroons:     macaroon.Slice{mac},
   507  			},
   508  		}},
   509  	}
   510  	s.waitForWorkerStubCalls(c, expected)
   511  }
   512  
   513  func (s *remoteRelationsSuite) TestRemoteNotFoundTerminatesOnPublish(c *gc.C) {
   514  	w := s.assertRemoteRelationsWorkers(c)
   515  	defer workertest.CleanKill(c, w)
   516  	s.stub.ResetCalls()
   517  
   518  	s.stub.SetErrors(nil, params.Error{Code: params.CodeNotFound})
   519  
   520  	unitsWatcher, _ := s.relationsFacade.relationsUnitsWatcher("db2:db django:db")
   521  	unitsWatcher.changes <- watcher.RelationUnitsChange{
   522  		Changed:  map[string]watcher.UnitSettings{"unit/1": {Version: 2}},
   523  		Departed: []string{"unit/2"},
   524  	}
   525  
   526  	mac, err := apitesting.NewMacaroon("apimac")
   527  	c.Assert(err, jc.ErrorIsNil)
   528  	expected := []jujutesting.StubCall{
   529  		{"RelationUnitSettings", []interface{}{
   530  			[]params.RelationUnit{{
   531  				Relation: "relation-db2.db#django.db",
   532  				Unit:     "unit-unit-1"}}}},
   533  		{"PublishRelationChange", []interface{}{
   534  			params.RemoteRelationChangeEvent{
   535  				ApplicationToken: "token-django",
   536  				RelationToken:    "token-db2:db django:db",
   537  				ChangedUnits: []params.RemoteRelationUnitChange{{
   538  					UnitId:   1,
   539  					Settings: map[string]interface{}{"foo": "bar"},
   540  				}},
   541  				DepartedUnits: []int{2},
   542  				Macaroons:     macaroon.Slice{mac},
   543  			},
   544  		}},
   545  		{"SetRemoteApplicationStatus", []interface{}{"db2", "terminated", "offer has been removed"}},
   546  		{"Close", nil},
   547  	}
   548  	s.waitForWorkerStubCalls(c, expected)
   549  }
   550  
   551  func (s *remoteRelationsSuite) TestRemoteRelationsChangedConsumes(c *gc.C) {
   552  	w := s.assertRemoteRelationsWorkers(c)
   553  	defer workertest.CleanKill(c, w)
   554  	s.stub.ResetCalls()
   555  
   556  	unitsWatcher, _ := s.remoteRelationsFacade.relationsUnitsWatcher("token-db2:db django:db")
   557  	unitsWatcher.changes <- watcher.RelationUnitsChange{
   558  		Changed:  map[string]watcher.UnitSettings{"unit/1": {Version: 2}},
   559  		Departed: []string{"unit/2"},
   560  	}
   561  
   562  	mac, err := apitesting.NewMacaroon("apimac")
   563  	c.Assert(err, jc.ErrorIsNil)
   564  	expected := []jujutesting.StubCall{
   565  		{"RelationUnitSettings", []interface{}{
   566  			[]params.RemoteRelationUnit{{
   567  				RelationToken: "token-db2:db django:db",
   568  				Unit:          "unit-unit-1",
   569  				Macaroons:     macaroon.Slice{mac}}}}},
   570  		{"ConsumeRemoteRelationChange", []interface{}{
   571  			params.RemoteRelationChangeEvent{
   572  				ApplicationToken: "token-offer-db2-uuid",
   573  				RelationToken:    "token-db2:db django:db",
   574  				ChangedUnits: []params.RemoteRelationUnitChange{{
   575  					UnitId:   1,
   576  					Settings: map[string]interface{}{"foo": "bar"},
   577  				}},
   578  				DepartedUnits: []int{2},
   579  				Macaroons:     macaroon.Slice{mac},
   580  			},
   581  		}},
   582  	}
   583  	s.waitForWorkerStubCalls(c, expected)
   584  }
   585  
   586  func (s *remoteRelationsSuite) TestRemoteRelationsDyingConsumes(c *gc.C) {
   587  	w := s.assertRemoteRelationsWorkers(c)
   588  	defer workertest.CleanKill(c, w)
   589  	s.stub.ResetCalls()
   590  
   591  	statusWatcher, _ := s.remoteRelationsFacade.relationsStatusWatcher("token-db2:db django:db")
   592  	statusWatcher.changes <- []watcher.RelationStatusChange{{
   593  		Life: life.Dying,
   594  	}}
   595  
   596  	suspended := false
   597  	expected := []jujutesting.StubCall{
   598  		{"ConsumeRemoteRelationChange", []interface{}{
   599  			params.RemoteRelationChangeEvent{
   600  				Life:             params.Dying,
   601  				ApplicationToken: "token-offer-db2-uuid",
   602  				RelationToken:    "token-db2:db django:db",
   603  				Suspended:        &suspended,
   604  			},
   605  		}},
   606  	}
   607  	s.waitForWorkerStubCalls(c, expected)
   608  }
   609  
   610  func (s *remoteRelationsSuite) TestRemoteRelationsChangedError(c *gc.C) {
   611  	s.assertRemoteRelationsChangedError(c, false)
   612  }
   613  
   614  func (s *remoteRelationsSuite) TestRemoteDyingRelationsChangedError(c *gc.C) {
   615  	s.assertRemoteRelationsChangedError(c, true)
   616  }
   617  
   618  func (s *remoteRelationsSuite) assertRemoteRelationsChangedError(c *gc.C, dying bool) {
   619  	w := s.assertRemoteRelationsWorkers(c)
   620  	defer workertest.CleanKill(c, w)
   621  	s.stub.ResetCalls()
   622  
   623  	s.stub.SetErrors(errors.New("failed"))
   624  	unitsWatcher, _ := s.relationsFacade.relationsUnitsWatcher("db2:db django:db")
   625  	unitsWatcher.changes <- watcher.RelationUnitsChange{
   626  		Departed: []string{"unit/1"},
   627  	}
   628  
   629  	// The error causes relation change publication to fail.
   630  	apiMac, err := apitesting.NewMacaroon("apimac")
   631  	c.Assert(err, jc.ErrorIsNil)
   632  	expected := []jujutesting.StubCall{
   633  		{"PublishRelationChange", []interface{}{
   634  			params.RemoteRelationChangeEvent{
   635  				ApplicationToken: "token-django",
   636  				RelationToken:    "token-db2:db django:db",
   637  				DepartedUnits:    []int{1},
   638  				Macaroons:        macaroon.Slice{apiMac},
   639  			},
   640  		}},
   641  		{"Close", nil},
   642  	}
   643  
   644  	s.waitForWorkerStubCalls(c, expected)
   645  	// An error in one of the units watchers does not kill the parent worker.
   646  	workertest.CheckAlive(c, w)
   647  
   648  	// Allow the worker to resume.
   649  	s.stub.SetErrors(nil)
   650  	s.stub.ResetCalls()
   651  	s.config.Clock.(*testclock.Clock).WaitAdvance(50*time.Second, coretesting.LongWait, 1)
   652  	// Not resumed yet.
   653  	c.Assert(s.stub.Calls(), gc.HasLen, 0)
   654  	s.config.Clock.(*testclock.Clock).WaitAdvance(10*time.Second, coretesting.LongWait, 1)
   655  
   656  	mac, err := apitesting.NewMacaroon("test")
   657  	c.Assert(err, jc.ErrorIsNil)
   658  	expected = []jujutesting.StubCall{
   659  		{"WatchRemoteApplicationRelations", []interface{}{"db2"}},
   660  		{"ControllerAPIInfoForModel", []interface{}{"remote-model-uuid"}},
   661  		{"WatchOfferStatus", []interface{}{"offer-db2-uuid", macaroon.Slice{mac}}},
   662  	}
   663  	s.waitForWorkerStubCalls(c, expected)
   664  	s.stub.ResetCalls()
   665  
   666  	relTag := names.NewRelationTag("db2:db django:db")
   667  	expected = []jujutesting.StubCall{
   668  		{"Relations", []interface{}{[]string{"db2:db django:db"}}},
   669  		{"ExportEntities", []interface{}{
   670  			[]names.Tag{names.NewApplicationTag("django"), relTag}}},
   671  		{"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{
   672  			ApplicationToken: "token-django",
   673  			SourceModelTag:   "model-local-model-uuid",
   674  			RelationToken:    "token-db2:db django:db",
   675  			RemoteEndpoint: params.RemoteEndpoint{
   676  				Name:      "db2",
   677  				Role:      "requires",
   678  				Interface: "db2",
   679  			},
   680  			OfferUUID:         "offer-db2-uuid",
   681  			LocalEndpointName: "data",
   682  			Macaroons:         macaroon.Slice{mac},
   683  		}}}},
   684  		{"SaveMacaroon", []interface{}{relTag, apiMac}},
   685  		{"ImportRemoteEntity", []interface{}{names.NewApplicationTag("db2"), "token-offer-db2-uuid"}},
   686  		{"WatchRelationSuspendedStatus", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}},
   687  		{"WatchLocalRelationUnits", []interface{}{"db2:db django:db"}},
   688  		{"WatchRelationUnits", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}},
   689  	}
   690  
   691  	// If a relation is dying and there's been an error, when processing resumes
   692  	// a cleanup is forced on the remote side.
   693  	if dying {
   694  		s.relationsFacade.updateRelationLife("db2:db django:db", params.Dying)
   695  		forceCleanup := true
   696  		expected = append(expected, jujutesting.StubCall{
   697  			FuncName: "PublishRelationChange",
   698  			Args: []interface{}{
   699  				params.RemoteRelationChangeEvent{
   700  					ApplicationToken: "token-django",
   701  					RelationToken:    "token-db2:db django:db",
   702  					Life:             params.Dying,
   703  					Macaroons:        macaroon.Slice{apiMac},
   704  					ForceCleanup:     &forceCleanup,
   705  				},
   706  			}},
   707  		)
   708  	}
   709  
   710  	relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2")
   711  	relWatcher.changes <- []string{"db2:db django:db"}
   712  
   713  	// After the worker resumes, normal processing happens.
   714  	s.waitForWorkerStubCalls(c, expected)
   715  }
   716  
   717  func (s *remoteRelationsSuite) TestRegisteredApplicationNotRegistered(c *gc.C) {
   718  	s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123)
   719  	db2app := newMockRemoteApplication("db2", "db2url")
   720  	db2app.registered = true
   721  	s.relationsFacade.remoteApplications["db2"] = db2app
   722  	applicationNames := []string{"db2"}
   723  	s.relationsFacade.remoteApplicationsWatcher.changes <- applicationNames
   724  
   725  	w, err := remoterelations.New(s.config)
   726  	c.Assert(err, jc.ErrorIsNil)
   727  	defer workertest.CleanKill(c, w)
   728  
   729  	expected := []jujutesting.StubCall{
   730  		{"WatchRemoteApplications", nil},
   731  		{"RemoteApplications", []interface{}{[]string{"db2"}}},
   732  		{"WatchRemoteApplicationRelations", []interface{}{"db2"}},
   733  	}
   734  	s.waitForWorkerStubCalls(c, expected)
   735  	s.stub.ResetCalls()
   736  
   737  	s.relationsFacade.relationsEndpoints["db2:db django:db"] = &relationEndpointInfo{
   738  		localApplicationName: "django",
   739  		localEndpoint: params.RemoteEndpoint{
   740  			Name:      "db2",
   741  			Role:      "requires",
   742  			Interface: "db2",
   743  		},
   744  		remoteEndpointName: "data",
   745  	}
   746  
   747  	relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2")
   748  	relWatcher.changes <- []string{"db2:db django:db"}
   749  
   750  	expected = []jujutesting.StubCall{
   751  		{"Relations", []interface{}{[]string{"db2:db django:db"}}},
   752  	}
   753  	s.waitForWorkerStubCalls(c, expected)
   754  }
   755  
   756  func (s *remoteRelationsSuite) TestRemoteRelationSuspended(c *gc.C) {
   757  	w := s.assertRemoteRelationsWorkers(c)
   758  	defer workertest.CleanKill(c, w)
   759  	s.stub.ResetCalls()
   760  
   761  	// First suspend the relation.
   762  	s.relationsFacade.relations["db2:db django:db"].SetSuspended(true)
   763  	relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2")
   764  	relWatcher.changes <- []string{"db2:db django:db"}
   765  
   766  	expected := []jujutesting.StubCall{
   767  		{"Relations", []interface{}{[]string{"db2:db django:db"}}},
   768  	}
   769  	s.waitForWorkerStubCalls(c, expected)
   770  	s.stub.ResetCalls()
   771  
   772  	// Now resume the relation.
   773  	s.relationsFacade.relations["db2:db django:db"].SetSuspended(false)
   774  	relWatcher.changes <- []string{"db2:db django:db"}
   775  
   776  	mac, err := apitesting.NewMacaroon("test")
   777  	c.Assert(err, jc.ErrorIsNil)
   778  	apiMac, err := apitesting.NewMacaroon("apimac")
   779  	relTag := names.NewRelationTag("db2:db django:db")
   780  	// When resuming, it's similar to setting things up for a new relation
   781  	// except that the call to create te life/status listener is missing.
   782  	expected = []jujutesting.StubCall{
   783  		{"Relations", []interface{}{[]string{"db2:db django:db"}}},
   784  		{"ExportEntities", []interface{}{
   785  			[]names.Tag{names.NewApplicationTag("django"), relTag}}},
   786  		{"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{
   787  			ApplicationToken: "token-django",
   788  			SourceModelTag:   "model-local-model-uuid",
   789  			RelationToken:    "token-db2:db django:db",
   790  			RemoteEndpoint: params.RemoteEndpoint{
   791  				Name:      "db2",
   792  				Role:      "requires",
   793  				Interface: "db2",
   794  			},
   795  			OfferUUID:         "offer-db2-uuid",
   796  			LocalEndpointName: "data",
   797  			Macaroons:         macaroon.Slice{mac},
   798  		}}}},
   799  		{"SaveMacaroon", []interface{}{relTag, apiMac}},
   800  		{"ImportRemoteEntity", []interface{}{names.NewApplicationTag("db2"), "token-offer-db2-uuid"}},
   801  		{"WatchLocalRelationUnits", []interface{}{"db2:db django:db"}},
   802  		{"WatchRelationUnits", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}},
   803  	}
   804  	s.waitForWorkerStubCalls(c, expected)
   805  }