github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/relation_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/charm/v12"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names/v5"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils/v3"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/core/crossmodel"
    18  	"github.com/juju/juju/core/permission"
    19  	"github.com/juju/juju/core/secrets"
    20  	"github.com/juju/juju/core/status"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/state/testing"
    23  	coretesting "github.com/juju/juju/testing"
    24  	"github.com/juju/juju/testing/factory"
    25  )
    26  
    27  type RelationSuite struct {
    28  	ConnSuite
    29  }
    30  
    31  var _ = gc.Suite(&RelationSuite{})
    32  
    33  func (s *RelationSuite) TestAddRelationErrors(c *gc.C) {
    34  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
    35  	wordpressEP, err := wordpress.Endpoint("db")
    36  	c.Assert(err, jc.ErrorIsNil)
    37  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
    38  	mysqlEP, err := mysql.Endpoint("server")
    39  	c.Assert(err, jc.ErrorIsNil)
    40  	riak := s.AddTestingApplication(c, "riak", s.AddTestingCharm(c, "riak"))
    41  	riakEP, err := riak.Endpoint("ring")
    42  	c.Assert(err, jc.ErrorIsNil)
    43  
    44  	// Check we can't add a relation with application that don't exist.
    45  	yoursqlEP := mysqlEP
    46  	yoursqlEP.ApplicationName = "yoursql"
    47  	_, err = s.State.AddRelation(yoursqlEP, wordpressEP)
    48  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db yoursql:server": application "yoursql" does not exist`)
    49  	assertNoRelations(c, wordpress)
    50  	assertNoRelations(c, mysql)
    51  
    52  	// Check that interfaces have to match.
    53  	msep3 := mysqlEP
    54  	msep3.Interface = "roflcopter"
    55  	_, err = s.State.AddRelation(msep3, wordpressEP)
    56  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": endpoints do not relate`)
    57  	assertNoRelations(c, wordpress)
    58  	assertNoRelations(c, mysql)
    59  
    60  	// Check a variety of surprising endpoint combinations.
    61  	_, err = s.State.AddRelation(wordpressEP)
    62  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db": relation must have two endpoints`)
    63  	assertNoRelations(c, wordpress)
    64  
    65  	_, err = s.State.AddRelation(riakEP, wordpressEP)
    66  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db riak:ring": endpoints do not relate`)
    67  	assertOneRelation(c, riak, 0, riakEP)
    68  	assertNoRelations(c, wordpress)
    69  
    70  	_, err = s.State.AddRelation(riakEP, riakEP)
    71  	c.Assert(err, gc.ErrorMatches, `cannot add relation "riak:ring riak:ring": endpoints do not relate`)
    72  	assertOneRelation(c, riak, 0, riakEP)
    73  
    74  	_, err = s.State.AddRelation()
    75  	c.Assert(err, gc.ErrorMatches, `cannot add relation "": relation must have two endpoints`)
    76  	_, err = s.State.AddRelation(mysqlEP, wordpressEP, riakEP)
    77  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server riak:ring": relation must have two endpoints`)
    78  	assertOneRelation(c, riak, 0, riakEP)
    79  	assertNoRelations(c, wordpress)
    80  	assertNoRelations(c, mysql)
    81  
    82  	// Check that a relation can't be added to a Dying application.
    83  	_, err = wordpress.AddUnit(state.AddUnitParams{})
    84  	c.Assert(err, jc.ErrorIsNil)
    85  	err = wordpress.Destroy()
    86  	c.Assert(err, jc.ErrorIsNil)
    87  	_, err = s.State.AddRelation(mysqlEP, wordpressEP)
    88  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": application "wordpress" is not alive`)
    89  	assertNoRelations(c, wordpress)
    90  	assertNoRelations(c, mysql)
    91  }
    92  
    93  func (s *StateSuite) TestAddRelationWithMaxLimit(c *gc.C) {
    94  	s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
    95  	s.AddTestingApplication(c, "mariadb", s.AddTestingCharm(c, "mariadb"))
    96  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
    97  
    98  	// First relation should be established without an issue
    99  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
   100  	c.Assert(err, jc.ErrorIsNil)
   101  	_, err = s.State.AddRelation(eps...)
   102  	c.Assert(err, jc.ErrorIsNil)
   103  
   104  	// Attempting to add a new relation between wordpress and mariadb
   105  	// should fail because wordpress:db specifies a max relation limit of 1
   106  	eps, err = s.State.InferEndpoints("wordpress", "mariadb")
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	_, err = s.State.AddRelation(eps...)
   109  	c.Assert(err, jc.Satisfies, errors.IsQuotaLimitExceeded, gc.Commentf("expected second AddRelation attempt to fail due to the limit:1 entry in the wordpress charm's metadata.yaml"))
   110  }
   111  
   112  func (s *RelationSuite) TestRetrieveSuccess(c *gc.C) {
   113  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   114  	wordpressEP, err := wordpress.Endpoint("db")
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   117  	mysqlUnit, err := mysql.AddUnit(state.AddUnitParams{})
   118  	c.Assert(err, jc.ErrorIsNil)
   119  	mysqlEP, err := mysql.Endpoint("server")
   120  	c.Assert(err, jc.ErrorIsNil)
   121  	expect, err := s.State.AddRelation(wordpressEP, mysqlEP)
   122  	c.Assert(err, jc.ErrorIsNil)
   123  	mysqlru, err := expect.Unit(mysqlUnit)
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	err = mysqlru.EnterScope(nil)
   126  	c.Assert(err, jc.ErrorIsNil)
   127  
   128  	rel, err := s.State.EndpointsRelation(wordpressEP, mysqlEP)
   129  	check := func() {
   130  		c.Assert(err, jc.ErrorIsNil)
   131  		c.Assert(rel.Id(), gc.Equals, expect.Id())
   132  		c.Assert(rel.String(), gc.Equals, expect.String())
   133  		c.Assert(rel.UnitCount(), gc.Equals, 1)
   134  	}
   135  	check()
   136  	rel, err = s.State.EndpointsRelation(mysqlEP, wordpressEP)
   137  	check()
   138  	rel, err = s.State.Relation(expect.Id())
   139  	check()
   140  }
   141  
   142  func (s *RelationSuite) TestRetrieveNotFound(c *gc.C) {
   143  	subway := state.Endpoint{
   144  		ApplicationName: "subway",
   145  		Relation: charm.Relation{
   146  			Name:      "db",
   147  			Interface: "mongodb",
   148  			Role:      charm.RoleRequirer,
   149  			Scope:     charm.ScopeGlobal,
   150  		},
   151  	}
   152  	mongo := state.Endpoint{
   153  		ApplicationName: "mongo",
   154  		Relation: charm.Relation{
   155  			Name:      "server",
   156  			Interface: "mongodb",
   157  			Role:      charm.RoleProvider,
   158  			Scope:     charm.ScopeGlobal,
   159  		},
   160  	}
   161  	_, err := s.State.EndpointsRelation(subway, mongo)
   162  	c.Assert(err, gc.ErrorMatches, `relation "subway:db mongo:server" not found`)
   163  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   164  
   165  	_, err = s.State.Relation(999)
   166  	c.Assert(err, gc.ErrorMatches, `relation 999 not found`)
   167  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   168  }
   169  
   170  func (s *RelationSuite) TestAddRelation(c *gc.C) {
   171  	// Add a relation.
   172  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   173  	wordpressEP, err := wordpress.Endpoint("db")
   174  	c.Assert(err, jc.ErrorIsNil)
   175  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   176  	mysqlEP, err := mysql.Endpoint("server")
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	_, err = s.State.AddRelation(wordpressEP, mysqlEP)
   179  	c.Assert(err, jc.ErrorIsNil)
   180  	assertOneRelation(c, mysql, 0, mysqlEP, wordpressEP)
   181  	assertOneRelation(c, wordpress, 0, wordpressEP, mysqlEP)
   182  
   183  	// Check we cannot re-add the same relation, regardless of endpoint ordering.
   184  	_, err = s.State.AddRelation(mysqlEP, wordpressEP)
   185  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation wordpress:db mysql:server`)
   186  	_, err = s.State.AddRelation(wordpressEP, mysqlEP)
   187  	c.Assert(err, gc.ErrorMatches, `cannot add relation "wordpress:db mysql:server": relation wordpress:db mysql:server`)
   188  	assertOneRelation(c, mysql, 0, mysqlEP, wordpressEP)
   189  	assertOneRelation(c, wordpress, 0, wordpressEP, mysqlEP)
   190  }
   191  
   192  func (s *RelationSuite) TestAddRelationSeriesNeedNotMatch(c *gc.C) {
   193  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   194  	wordpressEP, err := wordpress.Endpoint("db")
   195  	c.Assert(err, jc.ErrorIsNil)
   196  	mysql := s.AddTestingApplication(c, "mysql", s.AddSeriesCharm(c, "mysql", "bionic"))
   197  	mysqlEP, err := mysql.Endpoint("server")
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	_, err = s.State.AddRelation(wordpressEP, mysqlEP)
   200  	c.Assert(err, jc.ErrorIsNil)
   201  	assertOneRelation(c, mysql, 0, mysqlEP, wordpressEP)
   202  	assertOneRelation(c, wordpress, 0, wordpressEP, mysqlEP)
   203  }
   204  
   205  func (s *RelationSuite) TestAddContainerRelation(c *gc.C) {
   206  	// Add a relation.
   207  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   208  	wordpressEP, err := wordpress.Endpoint("juju-info")
   209  	c.Assert(err, jc.ErrorIsNil)
   210  	logging := s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
   211  	loggingEP, err := logging.Endpoint("info")
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	_, err = s.State.AddRelation(wordpressEP, loggingEP)
   214  	c.Assert(err, jc.ErrorIsNil)
   215  
   216  	// Check that the endpoints both have container scope.
   217  	wordpressEP.Scope = charm.ScopeContainer
   218  	assertOneRelation(c, logging, 0, loggingEP, wordpressEP)
   219  	assertOneRelation(c, wordpress, 0, wordpressEP, loggingEP)
   220  
   221  	// Check we cannot re-add the same relation, regardless of endpoint ordering.
   222  	_, err = s.State.AddRelation(loggingEP, wordpressEP)
   223  	c.Assert(err, gc.ErrorMatches, `cannot add relation "logging:info wordpress:juju-info": relation logging:info wordpress:juju-info`)
   224  	_, err = s.State.AddRelation(wordpressEP, loggingEP)
   225  	c.Assert(err, gc.ErrorMatches, `cannot add relation "logging:info wordpress:juju-info": relation logging:info wordpress:juju-info`)
   226  	assertOneRelation(c, logging, 0, loggingEP, wordpressEP)
   227  	assertOneRelation(c, wordpress, 0, wordpressEP, loggingEP)
   228  }
   229  
   230  func (s *RelationSuite) TestAddContainerRelationSeriesMustMatch(c *gc.C) {
   231  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   232  	wordpressEP, err := wordpress.Endpoint("juju-info")
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	logging := s.AddTestingApplication(c, "logging", s.AddSeriesCharm(c, "logging-raring", "bionic"))
   235  	loggingEP, err := logging.Endpoint("info")
   236  	c.Assert(err, jc.ErrorIsNil)
   237  	_, err = s.State.AddRelation(wordpressEP, loggingEP)
   238  	c.Assert(err, gc.ErrorMatches, `cannot add relation "logging:info wordpress:juju-info": principal and subordinate applications' bases must match`)
   239  }
   240  
   241  func (s *RelationSuite) TestAddContainerRelationMultiSeriesMatch(c *gc.C) {
   242  	principal := s.AddTestingApplication(c, "multi-series", s.AddSeriesCharm(c, "multi-series", "quantal"))
   243  	principalEP, err := principal.Endpoint("multi-directory")
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	subord := s.AddTestingApplication(c, "multi-series-subordinate", s.AddSeriesCharm(c, "multi-series-subordinate", "bionic"))
   246  	subordEP, err := subord.Endpoint("multi-directory")
   247  	c.Assert(err, jc.ErrorIsNil)
   248  
   249  	_, err = s.State.AddRelation(principalEP, subordEP)
   250  	principalEP.Scope = charm.ScopeContainer
   251  	c.Assert(err, jc.ErrorIsNil)
   252  	assertOneRelation(c, subord, 0, subordEP, principalEP)
   253  	assertOneRelation(c, principal, 0, principalEP, subordEP)
   254  }
   255  
   256  func (s *RelationSuite) TestAddContainerRelationMultiSeriesNoMatch(c *gc.C) {
   257  	principal := s.AddTestingApplication(c, "multi-series", s.AddTestingCharm(c, "multi-series"))
   258  	principalEP, err := principal.Endpoint("multi-directory")
   259  	c.Assert(err, jc.ErrorIsNil)
   260  	meta := `
   261  name: multi-series-subordinate
   262  summary: a test charm
   263  description: a test
   264  subordinate: true
   265  series:
   266      - xenial
   267  requires:
   268      multi-directory:
   269         interface: logging
   270         scope: container
   271  `[1:]
   272  	subord := s.AddTestingApplication(c, "multi-series-subordinate", s.AddMetaCharm(c, "multi-series-subordinate", meta, 1))
   273  	subordEP, err := subord.Endpoint("multi-directory")
   274  	c.Assert(err, jc.ErrorIsNil)
   275  
   276  	_, err = s.State.AddRelation(principalEP, subordEP)
   277  	principalEP.Scope = charm.ScopeContainer
   278  	c.Assert(err, gc.ErrorMatches, `cannot add relation "multi-series-subordinate:multi-directory multi-series:multi-directory": principal and subordinate applications' bases must match`)
   279  }
   280  
   281  func (s *RelationSuite) TestAddContainerRelationWithNoSubordinate(c *gc.C) {
   282  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   283  	wordpressSubEP, err := wordpress.Endpoint("db")
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	wordpressSubEP.Scope = charm.ScopeContainer
   286  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   287  	mysqlEP, err := mysql.Endpoint("server")
   288  	c.Assert(err, jc.ErrorIsNil)
   289  
   290  	_, err = s.State.AddRelation(mysqlEP, wordpressSubEP)
   291  	c.Assert(err, gc.ErrorMatches,
   292  		`cannot add relation "wordpress:db mysql:server": container scoped relation requires at least one subordinate application`)
   293  	assertNoRelations(c, wordpress)
   294  	assertNoRelations(c, mysql)
   295  }
   296  
   297  func (s *RelationSuite) TestAddContainerRelationWithTwoSubordinates(c *gc.C) {
   298  	loggingCharm := s.AddTestingCharm(c, "logging")
   299  	logging1 := s.AddTestingApplication(c, "logging1", loggingCharm)
   300  	logging1EP, err := logging1.Endpoint("juju-info")
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	logging2 := s.AddTestingApplication(c, "logging2", loggingCharm)
   303  	logging2EP, err := logging2.Endpoint("info")
   304  	c.Assert(err, jc.ErrorIsNil)
   305  
   306  	_, err = s.State.AddRelation(logging1EP, logging2EP)
   307  	c.Assert(err, jc.ErrorIsNil)
   308  	// AddRelation changes the scope on the endpoint if relation is container scoped.
   309  	logging1EP.Scope = charm.ScopeContainer
   310  	assertOneRelation(c, logging1, 0, logging1EP, logging2EP)
   311  	assertOneRelation(c, logging2, 0, logging2EP, logging1EP)
   312  }
   313  
   314  func (s *RelationSuite) TestDestroyRelation(c *gc.C) {
   315  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   316  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   317  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
   318  	c.Assert(err, jc.ErrorIsNil)
   319  	rel, err := s.State.AddRelation(eps...)
   320  	c.Assert(err, jc.ErrorIsNil)
   321  
   322  	// Test that the relation can be destroyed.
   323  	err = rel.Destroy()
   324  	c.Assert(err, jc.ErrorIsNil)
   325  	err = rel.Refresh()
   326  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   327  	assertNoRelations(c, wordpress)
   328  	assertNoRelations(c, mysql)
   329  
   330  	// Check that a second destroy is a no-op.
   331  	err = rel.Destroy()
   332  	c.Assert(err, jc.ErrorIsNil)
   333  
   334  	// Create a new relation and check that refreshing the old does not find
   335  	// the new.
   336  	_, err = s.State.AddRelation(eps...)
   337  	c.Assert(err, jc.ErrorIsNil)
   338  	err = rel.Refresh()
   339  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   340  }
   341  
   342  func (s *RelationSuite) TestDestroyPeerRelation(c *gc.C) {
   343  	// Check that a peer relation cannot be destroyed directly.
   344  	riakch := s.AddTestingCharm(c, "riak")
   345  	riak := s.AddTestingApplication(c, "riak", riakch)
   346  	riakEP, err := riak.Endpoint("ring")
   347  	c.Assert(err, jc.ErrorIsNil)
   348  	rel := assertOneRelation(c, riak, 0, riakEP)
   349  	err = rel.Destroy()
   350  	c.Assert(err, gc.ErrorMatches, `cannot destroy relation "riak:ring": is a peer relation`)
   351  	assertOneRelation(c, riak, 0, riakEP)
   352  
   353  	// Check that it is destroyed when the application is destroyed.
   354  	err = riak.Destroy()
   355  	c.Assert(err, jc.ErrorIsNil)
   356  	assertNoRelations(c, riak)
   357  	err = rel.Refresh()
   358  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   359  
   360  	// Create a new application (and hence a new relation in the background); check
   361  	// that refreshing the old one does not accidentally get the new one.
   362  	newriak := s.AddTestingApplication(c, "riak", riakch)
   363  	assertOneRelation(c, newriak, 1, riakEP)
   364  	err = rel.Refresh()
   365  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   366  }
   367  
   368  func (s *RelationSuite) TestDestroyRelationIncorrectUnitCount(c *gc.C) {
   369  	prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal)
   370  	prr.allEnterScope(c)
   371  
   372  	rel := prr.rel
   373  	state.RemoveUnitRelations(c, rel)
   374  	err := rel.Refresh()
   375  	c.Assert(err, jc.ErrorIsNil)
   376  	c.Assert(rel.UnitCount(), gc.Not(gc.Equals), 0)
   377  
   378  	_, err = rel.DestroyWithForce(false, dontWait)
   379  	c.Assert(err, gc.ErrorMatches, ".*unit count mismatch on relation wordpress:db mysql:server: expected 4 units in scope but got 0")
   380  }
   381  
   382  func (s *RelationSuite) assertInScope(c *gc.C, relUnit *state.RelationUnit, inScope bool) {
   383  	ok, err := relUnit.InScope()
   384  	c.Assert(err, jc.ErrorIsNil)
   385  	c.Assert(ok, gc.Equals, inScope)
   386  }
   387  
   388  func (s *RelationSuite) assertDestroyCrossModelRelation(c *gc.C, appStatus *status.Status) {
   389  	rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{
   390  		Name:        "remote-wordpress",
   391  		SourceModel: names.NewModelTag("source-model"),
   392  		OfferUUID:   "offer-uuid",
   393  		Endpoints: []charm.Relation{{
   394  			Interface: "mysql",
   395  			Limit:     1,
   396  			Name:      "db",
   397  			Role:      charm.RoleRequirer,
   398  			Scope:     charm.ScopeGlobal,
   399  		}},
   400  	})
   401  	c.Assert(err, jc.ErrorIsNil)
   402  	wordpressEP, err := rwordpress.Endpoint("db")
   403  	c.Assert(err, jc.ErrorIsNil)
   404  
   405  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   406  	mysqlUnit, err := mysql.AddUnit(state.AddUnitParams{})
   407  	c.Assert(err, jc.ErrorIsNil)
   408  	mysqlEP, err := mysql.Endpoint("server")
   409  	c.Assert(err, jc.ErrorIsNil)
   410  
   411  	rel, err := s.State.AddRelation(wordpressEP, mysqlEP)
   412  	c.Assert(err, jc.ErrorIsNil)
   413  	mysqlru, err := rel.Unit(mysqlUnit)
   414  	c.Assert(err, jc.ErrorIsNil)
   415  	err = mysqlru.EnterScope(nil)
   416  	c.Assert(err, jc.ErrorIsNil)
   417  	s.assertInScope(c, mysqlru, true)
   418  
   419  	wpru, err := rel.RemoteUnit("remote-wordpress/0")
   420  	c.Assert(err, jc.ErrorIsNil)
   421  	err = wpru.EnterScope(nil)
   422  	c.Assert(err, jc.ErrorIsNil)
   423  	s.assertInScope(c, wpru, true)
   424  
   425  	if appStatus != nil {
   426  		err = rwordpress.SetStatus(status.StatusInfo{Status: *appStatus})
   427  		c.Assert(err, jc.ErrorIsNil)
   428  	}
   429  
   430  	err = rel.Refresh()
   431  	c.Assert(err, jc.ErrorIsNil)
   432  	err = rel.Destroy()
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	err = rel.Refresh()
   435  	c.Assert(err, jc.ErrorIsNil)
   436  	c.Assert(rel.Life(), gc.Equals, state.Dying)
   437  
   438  	// If the remote app for the relation is terminated, any remote units are
   439  	// forcibly removed from scope, but not local ones.
   440  	s.assertInScope(c, wpru, appStatus == nil || *appStatus != status.Terminated)
   441  	s.assertInScope(c, mysqlru, true)
   442  }
   443  
   444  func (s *RelationSuite) TestDestroyCrossModelRelationNoAppStatus(c *gc.C) {
   445  	s.assertDestroyCrossModelRelation(c, nil)
   446  }
   447  
   448  func (s *RelationSuite) TestDestroyCrossModelRelationAppNotTerminated(c *gc.C) {
   449  	st := status.Active
   450  	s.assertDestroyCrossModelRelation(c, &st)
   451  }
   452  
   453  func (s *RelationSuite) TestDestroyCrossModelRelationAppTerminated(c *gc.C) {
   454  	st := status.Terminated
   455  	s.assertDestroyCrossModelRelation(c, &st)
   456  }
   457  
   458  func (s *RelationSuite) TestForceDestroyCrossModelRelationOfferSide(c *gc.C) {
   459  	rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{
   460  		Name:                   "remote-wordpress",
   461  		ExternalControllerUUID: "controller-uuid",
   462  		SourceModel:            names.NewModelTag("source-model"),
   463  		OfferUUID:              "offer-uuid",
   464  		Endpoints: []charm.Relation{{
   465  			Interface: "mysql",
   466  			Limit:     1,
   467  			Name:      "db",
   468  			Role:      charm.RoleRequirer,
   469  			Scope:     charm.ScopeGlobal,
   470  		}},
   471  		IsConsumerProxy: true,
   472  	})
   473  	c.Assert(err, jc.ErrorIsNil)
   474  	rc, err := state.ControllerRefCount(s.State, "controller-uuid")
   475  	c.Assert(err, jc.ErrorIsNil)
   476  	c.Assert(rc, gc.Equals, 1)
   477  	wordpressEP, err := rwordpress.Endpoint("db")
   478  	c.Assert(err, jc.ErrorIsNil)
   479  
   480  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   481  	mysqlUnit, err := mysql.AddUnit(state.AddUnitParams{})
   482  	c.Assert(err, jc.ErrorIsNil)
   483  	mysqlEP, err := mysql.Endpoint("server")
   484  	c.Assert(err, jc.ErrorIsNil)
   485  
   486  	rel, err := s.State.AddRelation(wordpressEP, mysqlEP)
   487  	c.Assert(err, jc.ErrorIsNil)
   488  	mysqlru, err := rel.Unit(mysqlUnit)
   489  	c.Assert(err, jc.ErrorIsNil)
   490  	err = mysqlru.EnterScope(nil)
   491  	c.Assert(err, jc.ErrorIsNil)
   492  	s.assertInScope(c, mysqlru, true)
   493  
   494  	wpru, err := rel.RemoteUnit("remote-wordpress/0")
   495  	c.Assert(err, jc.ErrorIsNil)
   496  	err = wpru.EnterScope(nil)
   497  	c.Assert(err, jc.ErrorIsNil)
   498  	s.assertInScope(c, wpru, true)
   499  
   500  	err = rel.Refresh()
   501  	c.Assert(err, jc.ErrorIsNil)
   502  	errs, err := rel.DestroyWithForce(true, 0)
   503  	c.Assert(errs, gc.HasLen, 0)
   504  	c.Assert(err, jc.ErrorIsNil)
   505  	err = rel.Refresh()
   506  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   507  	err = rwordpress.Refresh()
   508  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   509  	_, err = state.ControllerRefCount(s.State, "controller-uuid")
   510  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   511  
   512  	s.assertInScope(c, wpru, false)
   513  	s.assertInScope(c, mysqlru, true)
   514  }
   515  
   516  func (s *RelationSuite) TestIsCrossModelYup(c *gc.C) {
   517  	rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{
   518  		Name:            "remote-wordpress",
   519  		SourceModel:     names.NewModelTag("source-model"),
   520  		IsConsumerProxy: true,
   521  		OfferUUID:       "offer-uuid",
   522  		Endpoints: []charm.Relation{{
   523  			Interface: "mysql",
   524  			Limit:     1,
   525  			Name:      "db",
   526  			Role:      charm.RoleRequirer,
   527  			Scope:     charm.ScopeGlobal,
   528  		}},
   529  	})
   530  	c.Assert(err, jc.ErrorIsNil)
   531  	wordpressEP, err := rwordpress.Endpoint("db")
   532  	c.Assert(err, jc.ErrorIsNil)
   533  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   534  	mysqlEP, err := mysql.Endpoint("server")
   535  	c.Assert(err, jc.ErrorIsNil)
   536  	relation, err := s.State.AddRelation(wordpressEP, mysqlEP)
   537  	c.Assert(err, jc.ErrorIsNil)
   538  
   539  	app, result, err := relation.RemoteApplication()
   540  	c.Assert(err, jc.ErrorIsNil)
   541  	c.Assert(result, jc.IsTrue)
   542  	c.Assert(app.Name(), gc.Equals, "remote-wordpress")
   543  }
   544  
   545  func (s *RelationSuite) TestAddCrossModelNotAllowed(c *gc.C) {
   546  	rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{
   547  		Name:        "remote-wordpress",
   548  		SourceModel: names.NewModelTag("source-model"),
   549  		OfferUUID:   "offer-uuid",
   550  		Endpoints: []charm.Relation{{
   551  			Interface: "mysql",
   552  			Limit:     1,
   553  			Name:      "db",
   554  			Role:      charm.RoleRequirer,
   555  			Scope:     charm.ScopeGlobal,
   556  		}},
   557  	})
   558  	c.Assert(err, jc.ErrorIsNil)
   559  	wordpressEP, err := rwordpress.Endpoint("db")
   560  	c.Assert(err, jc.ErrorIsNil)
   561  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   562  	mysqlEP, err := mysql.Endpoint("server")
   563  	c.Assert(err, jc.ErrorIsNil)
   564  
   565  	err = rwordpress.SetStatus(status.StatusInfo{Status: status.Terminated})
   566  	c.Assert(err, jc.ErrorIsNil)
   567  	_, err = s.State.AddRelation(wordpressEP, mysqlEP)
   568  	c.Assert(err, gc.ErrorMatches, `cannot add relation "remote-wordpress:db mysql:server": remote offer remote-wordpress is terminated`)
   569  
   570  }
   571  
   572  func (s *RelationSuite) TestIsCrossModelNope(c *gc.C) {
   573  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   574  	wordpressEP, err := wordpress.Endpoint("db")
   575  	c.Assert(err, jc.ErrorIsNil)
   576  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   577  	mysqlEP, err := mysql.Endpoint("server")
   578  	c.Assert(err, jc.ErrorIsNil)
   579  	relation, err := s.State.AddRelation(wordpressEP, mysqlEP)
   580  	c.Assert(err, jc.ErrorIsNil)
   581  
   582  	app, result, err := relation.RemoteApplication()
   583  	c.Assert(err, jc.ErrorIsNil)
   584  	c.Assert(result, jc.IsFalse)
   585  	c.Assert(app, gc.IsNil)
   586  }
   587  
   588  func assertNoRelations(c *gc.C, app state.ApplicationEntity) {
   589  	rels, err := app.Relations()
   590  	c.Assert(err, jc.ErrorIsNil)
   591  	c.Assert(rels, gc.HasLen, 0)
   592  }
   593  
   594  func assertOneRelation(c *gc.C, srv *state.Application, relId int, endpoints ...state.Endpoint) *state.Relation {
   595  	rels, err := srv.Relations()
   596  	c.Assert(err, jc.ErrorIsNil)
   597  	c.Assert(rels, gc.HasLen, 1)
   598  
   599  	rel := rels[0]
   600  	c.Assert(rel.Id(), gc.Equals, relId)
   601  
   602  	c.Assert(rel.Endpoints(), jc.SameContents, endpoints)
   603  
   604  	name := srv.Name()
   605  	expectEp := endpoints[0]
   606  	ep, err := rel.Endpoint(name)
   607  	c.Assert(err, jc.ErrorIsNil)
   608  	c.Assert(ep, gc.DeepEquals, expectEp)
   609  	if len(endpoints) == 2 {
   610  		expectEp = endpoints[1]
   611  	}
   612  	eps, err := rel.RelatedEndpoints(name)
   613  	c.Assert(err, jc.ErrorIsNil)
   614  	c.Assert(eps, gc.DeepEquals, []state.Endpoint{expectEp})
   615  	return rel
   616  }
   617  
   618  func (s *RelationSuite) TestRemoveAlsoDeletesNetworks(c *gc.C) {
   619  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   620  	wordpressEP, err := wordpress.Endpoint("db")
   621  	c.Assert(err, jc.ErrorIsNil)
   622  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   623  	mysqlEP, err := mysql.Endpoint("server")
   624  	c.Assert(err, jc.ErrorIsNil)
   625  	relation, err := s.State.AddRelation(wordpressEP, mysqlEP)
   626  	c.Assert(err, jc.ErrorIsNil)
   627  
   628  	relIngress := state.NewRelationIngressNetworks(s.State)
   629  	_, err = relIngress.Save(relation.Tag().Id(), false, []string{"1.2.3.4/32", "4.3.2.1/16"})
   630  	c.Assert(err, jc.ErrorIsNil)
   631  	_, err = relIngress.Save(relation.Tag().Id(), true, []string{"1.2.3.4/32", "4.3.2.1/16"})
   632  	c.Assert(err, jc.ErrorIsNil)
   633  
   634  	relEgress := state.NewRelationEgressNetworks(s.State)
   635  	_, err = relEgress.Save(relation.Tag().Id(), false, []string{"1.2.3.4/32", "4.3.2.1/16"})
   636  	c.Assert(err, jc.ErrorIsNil)
   637  	_, err = relEgress.Save(relation.Tag().Id(), true, []string{"1.2.3.4/32", "4.3.2.1/16"})
   638  	c.Assert(err, jc.ErrorIsNil)
   639  
   640  	state.RemoveRelation(c, relation, false)
   641  	_, err = relIngress.Networks(relation.Tag().Id())
   642  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   643  	_, err = relEgress.Networks(relation.Tag().Id())
   644  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   645  }
   646  
   647  func (s *RelationSuite) TestRemoveAlsoDeletesRemoteTokens(c *gc.C) {
   648  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   649  	wordpressEP, err := wordpress.Endpoint("db")
   650  	c.Assert(err, jc.ErrorIsNil)
   651  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   652  	mysqlEP, err := mysql.Endpoint("server")
   653  	c.Assert(err, jc.ErrorIsNil)
   654  	relation, err := s.State.AddRelation(wordpressEP, mysqlEP)
   655  	c.Assert(err, jc.ErrorIsNil)
   656  
   657  	// Add remote token so we can check it is cleaned up.
   658  	re := s.State.RemoteEntities()
   659  	relToken, err := re.ExportLocalEntity(relation.Tag())
   660  	c.Assert(err, jc.ErrorIsNil)
   661  
   662  	state.RemoveRelation(c, relation, false)
   663  	_, err = re.GetToken(relation.Tag())
   664  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   665  	_, err = re.GetRemoteEntity(relToken)
   666  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   667  }
   668  
   669  func (s *RelationSuite) TestRemoveAlsoDeletesRemoteOfferConnections(c *gc.C) {
   670  	rwordpress, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{
   671  		Name:            "remote-wordpress",
   672  		SourceModel:     names.NewModelTag("source-model"),
   673  		IsConsumerProxy: true,
   674  		OfferUUID:       "offer-uuid",
   675  		Endpoints: []charm.Relation{{
   676  			Interface: "mysql",
   677  			Limit:     1,
   678  			Name:      "db",
   679  			Role:      charm.RoleRequirer,
   680  			Scope:     charm.ScopeGlobal,
   681  		}},
   682  	})
   683  	c.Assert(err, jc.ErrorIsNil)
   684  	wordpressEP, err := rwordpress.Endpoint("db")
   685  	c.Assert(err, jc.ErrorIsNil)
   686  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   687  	mysqlEP, err := mysql.Endpoint("server")
   688  	c.Assert(err, jc.ErrorIsNil)
   689  	relation, err := s.State.AddRelation(wordpressEP, mysqlEP)
   690  	c.Assert(err, jc.ErrorIsNil)
   691  
   692  	// Add a offer connection record so we can check it is cleaned up.
   693  	_, err = s.State.AddOfferConnection(state.AddOfferConnectionParams{
   694  		SourceModelUUID: coretesting.ModelTag.Id(),
   695  		RelationId:      relation.Id(),
   696  		RelationKey:     relation.Tag().Id(),
   697  		Username:        "fred",
   698  		OfferUUID:       "offer-uuid",
   699  	})
   700  	c.Assert(err, jc.ErrorIsNil)
   701  	rc, err := s.State.RemoteConnectionStatus("offer-uuid")
   702  	c.Assert(err, jc.ErrorIsNil)
   703  	c.Assert(rc.TotalConnectionCount(), gc.Equals, 1)
   704  
   705  	state.RemoveRelation(c, relation, false)
   706  	rc, err = s.State.RemoteConnectionStatus("offer-uuid")
   707  	c.Assert(err, jc.ErrorIsNil)
   708  	c.Assert(rc.TotalConnectionCount(), gc.Equals, 0)
   709  }
   710  
   711  func (s *RelationSuite) TestRemoveAlsoDeletesSecretPermissions(c *gc.C) {
   712  	relation := s.Factory.MakeRelation(c, nil)
   713  	app, err := s.State.Application(relation.Endpoints()[0].ApplicationName)
   714  	c.Assert(err, jc.ErrorIsNil)
   715  	store := state.NewSecrets(s.State)
   716  	uri := secrets.NewURI()
   717  	cp := state.CreateSecretParams{
   718  		Version: 1,
   719  		Owner:   app.Tag(),
   720  		UpdateSecretParams: state.UpdateSecretParams{
   721  			LeaderToken: &fakeToken{},
   722  			Data:        map[string]string{"foo": "bar"},
   723  		},
   724  	}
   725  	_, err = store.CreateSecret(uri, cp)
   726  	c.Assert(err, jc.ErrorIsNil)
   727  
   728  	subject := names.NewApplicationTag("wordpress")
   729  	err = s.State.GrantSecretAccess(uri, state.SecretAccessParams{
   730  		LeaderToken: &fakeToken{},
   731  		Scope:       relation.Tag(),
   732  		Subject:     subject,
   733  		Role:        secrets.RoleView,
   734  	})
   735  	c.Assert(err, jc.ErrorIsNil)
   736  	access, err := s.State.SecretAccess(uri, subject)
   737  	c.Assert(err, jc.ErrorIsNil)
   738  	c.Assert(access, gc.Equals, secrets.RoleView)
   739  
   740  	state.RemoveRelation(c, relation, false)
   741  	access, err = s.State.SecretAccess(uri, subject)
   742  	c.Assert(err, jc.ErrorIsNil)
   743  	c.Assert(access, gc.Equals, secrets.RoleNone)
   744  }
   745  
   746  func (s *RelationSuite) TestRemoveNoFeatureFlag(c *gc.C) {
   747  	s.SetFeatureFlags( /*none*/ )
   748  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   749  	wordpressEP, err := wordpress.Endpoint("db")
   750  	c.Assert(err, jc.ErrorIsNil)
   751  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   752  	mysqlEP, err := mysql.Endpoint("server")
   753  	c.Assert(err, jc.ErrorIsNil)
   754  	relation, err := s.State.AddRelation(wordpressEP, mysqlEP)
   755  	c.Assert(err, jc.ErrorIsNil)
   756  
   757  	state.RemoveRelation(c, relation, false)
   758  	_, err = s.State.KeyRelation(relation.Tag().Id())
   759  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   760  }
   761  
   762  func (s *RelationSuite) TestWatchLifeSuspendedStatus(c *gc.C) {
   763  	rel := s.setupRelationStatus(c)
   764  	mysql, err := s.State.Application("mysql")
   765  	c.Assert(err, jc.ErrorIsNil)
   766  	u, err := mysql.AddUnit(state.AddUnitParams{})
   767  	c.Assert(err, jc.ErrorIsNil)
   768  	m := s.Factory.MakeMachine(c, &factory.MachineParams{})
   769  	err = u.AssignToMachine(m)
   770  	c.Assert(err, jc.ErrorIsNil)
   771  	relUnit, err := rel.Unit(u)
   772  	c.Assert(err, jc.ErrorIsNil)
   773  	err = relUnit.EnterScope(nil)
   774  	c.Assert(err, jc.ErrorIsNil)
   775  
   776  	w := rel.WatchLifeSuspendedStatus()
   777  	defer testing.AssertStop(c, w)
   778  	wc := testing.NewStringsWatcherC(c, w)
   779  	// Initial event.
   780  	wc.AssertChange(rel.Tag().Id())
   781  	wc.AssertNoChange()
   782  
   783  	err = rel.SetSuspended(true, "reason")
   784  	c.Assert(err, jc.ErrorIsNil)
   785  	wc.AssertChange(rel.Tag().Id())
   786  	wc.AssertNoChange()
   787  
   788  	err = rel.Refresh()
   789  	c.Assert(err, jc.ErrorIsNil)
   790  	err = rel.Destroy()
   791  	c.Assert(err, jc.ErrorIsNil)
   792  	wc.AssertChange(rel.Tag().Id())
   793  	wc.AssertNoChange()
   794  }
   795  
   796  func (s *RelationSuite) TestWatchLifeSuspendedStatusDead(c *gc.C) {
   797  	// Create a pair of application and a relation between them.
   798  	s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   799  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   800  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
   801  	c.Assert(err, jc.ErrorIsNil)
   802  	rel, err := s.State.AddRelation(eps...)
   803  	c.Assert(err, jc.ErrorIsNil)
   804  
   805  	w := rel.WatchLifeSuspendedStatus()
   806  	defer testing.AssertStop(c, w)
   807  	wc := testing.NewStringsWatcherC(c, w)
   808  	wc.AssertChange(rel.Tag().Id())
   809  
   810  	err = rel.Destroy()
   811  	c.Assert(err, jc.ErrorIsNil)
   812  	wc.AssertChange(rel.Tag().Id())
   813  	wc.AssertNoChange()
   814  }
   815  
   816  func (s *RelationSuite) setupRelationStatus(c *gc.C) *state.Relation {
   817  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   818  	wordpressEP, err := wordpress.Endpoint("db")
   819  	c.Assert(err, jc.ErrorIsNil)
   820  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   821  	mysqlEP, err := mysql.Endpoint("server")
   822  	c.Assert(err, jc.ErrorIsNil)
   823  	rel, err := s.State.AddRelation(wordpressEP, mysqlEP)
   824  	c.Assert(err, jc.ErrorIsNil)
   825  	relStatus, err := rel.Status()
   826  	c.Assert(err, jc.ErrorIsNil)
   827  	c.Assert(relStatus.Status, gc.Equals, status.Joining)
   828  	ao := state.NewApplicationOffers(s.State)
   829  	offer, err := ao.AddOffer(crossmodel.AddApplicationOfferArgs{
   830  		OfferName:       "hosted-mysql",
   831  		ApplicationName: "mysql",
   832  		Owner:           s.Owner.Id(),
   833  	})
   834  	c.Assert(err, jc.ErrorIsNil)
   835  	user := s.Factory.MakeUser(c, &factory.UserParams{Name: "fred", Access: permission.WriteAccess})
   836  	_, err = s.State.AddOfferConnection(state.AddOfferConnectionParams{
   837  		SourceModelUUID: utils.MustNewUUID().String(),
   838  		OfferUUID:       offer.OfferUUID,
   839  		RelationKey:     rel.Tag().Id(),
   840  		RelationId:      rel.Id(),
   841  		Username:        user.Name(),
   842  	})
   843  	c.Assert(err, jc.ErrorIsNil)
   844  	return rel
   845  }
   846  
   847  func (s *RelationSuite) TestStatus(c *gc.C) {
   848  	rel := s.setupRelationStatus(c)
   849  	err := rel.SetStatus(status.StatusInfo{
   850  		Status:  status.Suspended,
   851  		Message: "for a while",
   852  	})
   853  	c.Assert(err, jc.ErrorIsNil)
   854  	relStatus, err := rel.Status()
   855  	c.Assert(err, jc.ErrorIsNil)
   856  	c.Assert(relStatus.Since, gc.NotNil)
   857  	relStatus.Since = nil
   858  	c.Assert(relStatus, jc.DeepEquals, status.StatusInfo{
   859  		Status:  status.Suspended,
   860  		Message: "for a while",
   861  		Data:    map[string]interface{}{},
   862  	})
   863  }
   864  
   865  func (s *RelationSuite) TestInvalidStatus(c *gc.C) {
   866  	rel := s.setupRelationStatus(c)
   867  
   868  	err := rel.SetStatus(status.StatusInfo{
   869  		Status: status.Status("invalid"),
   870  	})
   871  	c.Assert(err, gc.ErrorMatches, `cannot set invalid status "invalid"`)
   872  }
   873  
   874  func (s *RelationSuite) TestSetSuspend(c *gc.C) {
   875  	rel := s.setupRelationStatus(c)
   876  	// Suspend doesn't need an offer connection to be there.
   877  	state.RemoveOfferConnectionsForRelation(c, rel)
   878  	c.Assert(rel.Suspended(), jc.IsFalse)
   879  	err := rel.SetSuspended(true, "reason")
   880  	c.Assert(err, jc.ErrorIsNil)
   881  	rel, err = s.State.Relation(rel.Id())
   882  	c.Assert(err, jc.ErrorIsNil)
   883  	c.Assert(rel.Suspended(), jc.IsTrue)
   884  	c.Assert(rel.SuspendedReason(), gc.Equals, "reason")
   885  }
   886  
   887  func (s *RelationSuite) TestSetSuspendFalse(c *gc.C) {
   888  	rel := s.setupRelationStatus(c)
   889  	// Suspend doesn't need an offer connection to be there.
   890  	state.RemoveOfferConnectionsForRelation(c, rel)
   891  	c.Assert(rel.Suspended(), jc.IsFalse)
   892  	err := rel.SetSuspended(true, "reason")
   893  	c.Assert(err, jc.ErrorIsNil)
   894  	err = rel.SetSuspended(false, "reason")
   895  	c.Assert(err, gc.ErrorMatches, "cannot set suspended reason if not suspended")
   896  	err = rel.SetSuspended(false, "")
   897  	c.Assert(err, jc.ErrorIsNil)
   898  	rel, err = s.State.Relation(rel.Id())
   899  	c.Assert(err, jc.ErrorIsNil)
   900  	c.Assert(rel.Suspended(), jc.IsFalse)
   901  }
   902  
   903  func (s *RelationSuite) TestApplicationSettings(c *gc.C) {
   904  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   905  	s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   906  	eps, err := s.State.InferEndpoints("mysql", "wordpress")
   907  	c.Assert(err, jc.ErrorIsNil)
   908  	relation, err := s.State.AddRelation(eps...)
   909  	c.Assert(err, jc.ErrorIsNil)
   910  
   911  	settingsMap, err := relation.ApplicationSettings("mysql")
   912  	c.Assert(err, jc.ErrorIsNil)
   913  	c.Assert(settingsMap, gc.HasLen, 0)
   914  
   915  	settings := state.NewStateSettings(s.State)
   916  	key := fmt.Sprintf("r#%d#mysql", relation.Id())
   917  	err = settings.ReplaceSettings(key, map[string]interface{}{
   918  		"bailterspace": "blammo",
   919  	})
   920  	c.Assert(err, jc.ErrorIsNil)
   921  
   922  	settingsMap, err = relation.ApplicationSettings("mysql")
   923  	c.Assert(err, jc.ErrorIsNil)
   924  	c.Assert(settingsMap, gc.DeepEquals, map[string]interface{}{
   925  		"bailterspace": "blammo",
   926  	})
   927  }
   928  
   929  func (s *RelationSuite) TestApplicationSettingsPeer(c *gc.C) {
   930  	app := state.AddTestingApplication(c, s.State, "riak", state.AddTestingCharm(c, s.State, "riak"))
   931  	ep, err := app.Endpoint("ring")
   932  	c.Assert(err, jc.ErrorIsNil)
   933  	rel, err := s.State.EndpointsRelation(ep)
   934  	c.Assert(err, jc.ErrorIsNil)
   935  
   936  	settings := state.NewStateSettings(s.State)
   937  	key := fmt.Sprintf("r#%d#riak", rel.Id())
   938  	err = settings.ReplaceSettings(key, map[string]interface{}{
   939  		"mermaidens": "disappear",
   940  	})
   941  	c.Assert(err, jc.ErrorIsNil)
   942  
   943  	settingsMap, err := rel.ApplicationSettings("riak")
   944  	c.Assert(err, jc.ErrorIsNil)
   945  	c.Assert(settingsMap, gc.DeepEquals, map[string]interface{}{
   946  		"mermaidens": "disappear",
   947  	})
   948  }
   949  
   950  func (s *RelationSuite) TestApplicationSettingsErrors(c *gc.C) {
   951  	app := state.AddTestingApplication(c, s.State, "riak", state.AddTestingCharm(c, s.State, "riak"))
   952  	ep, err := app.Endpoint("ring")
   953  	c.Assert(err, jc.ErrorIsNil)
   954  	rel, err := s.State.EndpointsRelation(ep)
   955  	c.Assert(err, jc.ErrorIsNil)
   956  
   957  	state.AddTestingApplication(c, s.State, "wordpress", state.AddTestingCharm(c, s.State, "wordpress"))
   958  
   959  	settings, err := rel.ApplicationSettings("wordpress")
   960  	c.Assert(err, gc.ErrorMatches, `application "wordpress" is not a member of "riak:ring"`)
   961  	c.Assert(settings, gc.HasLen, 0)
   962  }
   963  
   964  func (s *RelationSuite) TestUpdateApplicationSettingsSuccess(c *gc.C) {
   965  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   966  	s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   967  	eps, err := s.State.InferEndpoints("mysql", "wordpress")
   968  	c.Assert(err, jc.ErrorIsNil)
   969  	relation, err := s.State.AddRelation(eps...)
   970  	c.Assert(err, jc.ErrorIsNil)
   971  
   972  	// fakeToken always succeeds.
   973  	err = relation.UpdateApplicationSettings(
   974  		"mysql", &fakeToken{}, map[string]interface{}{
   975  			"rendezvouse": "rendezvous",
   976  			"olden":       "yolk",
   977  		},
   978  	)
   979  	c.Assert(err, jc.ErrorIsNil)
   980  
   981  	settingsMap, err := relation.ApplicationSettings("mysql")
   982  	c.Assert(err, jc.ErrorIsNil)
   983  	c.Assert(settingsMap, gc.DeepEquals, map[string]interface{}{
   984  		"rendezvouse": "rendezvous",
   985  		"olden":       "yolk",
   986  	})
   987  
   988  	// Check that updates only overwrite existing keys.
   989  	err = relation.UpdateApplicationSettings(
   990  		"mysql", &fakeToken{}, map[string]interface{}{
   991  			"olden": "times",
   992  		},
   993  	)
   994  	c.Assert(err, jc.ErrorIsNil)
   995  	settingsMap, err = relation.ApplicationSettings("mysql")
   996  	c.Assert(err, jc.ErrorIsNil)
   997  	c.Assert(settingsMap, gc.DeepEquals, map[string]interface{}{
   998  		"rendezvouse": "rendezvous",
   999  		"olden":       "times",
  1000  	})
  1001  }
  1002  
  1003  func (s *RelationSuite) TestUpdateApplicationSettingsNotLeader(c *gc.C) {
  1004  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1005  	s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
  1006  	eps, err := s.State.InferEndpoints("mysql", "wordpress")
  1007  	c.Assert(err, jc.ErrorIsNil)
  1008  	relation, err := s.State.AddRelation(eps...)
  1009  	c.Assert(err, jc.ErrorIsNil)
  1010  
  1011  	err = relation.UpdateApplicationSettings(
  1012  		"mysql",
  1013  		&fakeToken{errors.New("not the leader")},
  1014  		map[string]interface{}{
  1015  			"rendezvouse": "rendezvous",
  1016  		},
  1017  	)
  1018  	c.Assert(err, gc.ErrorMatches,
  1019  		`relation "wordpress:db mysql:server" application "mysql": checking leadership continuity: not the leader`)
  1020  
  1021  	settingsMap, err := relation.ApplicationSettings("mysql")
  1022  	c.Assert(err, jc.ErrorIsNil)
  1023  	c.Assert(settingsMap, gc.HasLen, 0)
  1024  }
  1025  
  1026  func (s *RelationSuite) TestWatchApplicationSettings(c *gc.C) {
  1027  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1028  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
  1029  	eps, err := s.State.InferEndpoints("mysql", "wordpress")
  1030  	c.Assert(err, jc.ErrorIsNil)
  1031  	relation, err := s.State.AddRelation(eps...)
  1032  	c.Assert(err, jc.ErrorIsNil)
  1033  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
  1034  
  1035  	w, err := relation.WatchApplicationSettings(mysql)
  1036  	c.Assert(err, jc.ErrorIsNil)
  1037  	defer testing.AssertStop(c, w)
  1038  
  1039  	wc := testing.NewNotifyWatcherC(c, w)
  1040  	wc.AssertOneChange()
  1041  
  1042  	err = relation.UpdateApplicationSettings(
  1043  		"mysql", &fakeToken{}, map[string]interface{}{
  1044  			"castor": "pollux",
  1045  		},
  1046  	)
  1047  	c.Assert(err, jc.ErrorIsNil)
  1048  	wc.AssertOneChange()
  1049  
  1050  	// No notify for a null change.
  1051  	err = relation.UpdateApplicationSettings(
  1052  		"mysql", &fakeToken{}, map[string]interface{}{
  1053  			"castor": "pollux",
  1054  		},
  1055  	)
  1056  	c.Assert(err, jc.ErrorIsNil)
  1057  	wc.AssertNoChange()
  1058  }
  1059  
  1060  func (s *RelationSuite) TestWatchApplicationSettingsOtherEnd(c *gc.C) {
  1061  	s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1062  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
  1063  	eps, err := s.State.InferEndpoints("mysql", "wordpress")
  1064  	c.Assert(err, jc.ErrorIsNil)
  1065  	relation, err := s.State.AddRelation(eps...)
  1066  	c.Assert(err, jc.ErrorIsNil)
  1067  	s.WaitForModelWatchersIdle(c, s.Model.UUID())
  1068  
  1069  	w, err := relation.WatchApplicationSettings(mysql)
  1070  	c.Assert(err, jc.ErrorIsNil)
  1071  	defer testing.AssertStop(c, w)
  1072  
  1073  	wc := testing.NewNotifyWatcherC(c, w)
  1074  	wc.AssertOneChange()
  1075  
  1076  	// No notify if the other application's settings are changed.
  1077  	err = relation.UpdateApplicationSettings(
  1078  		"wordpress", &fakeToken{}, map[string]interface{}{
  1079  			"grand": "palais",
  1080  		},
  1081  	)
  1082  	c.Assert(err, jc.ErrorIsNil)
  1083  	wc.AssertNoChange()
  1084  }
  1085  
  1086  func (s *RelationSuite) TestDestroyForceSchedulesCleanupForStuckUnits(c *gc.C) {
  1087  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1088  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
  1089  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
  1090  	c.Assert(err, jc.ErrorIsNil)
  1091  	rel, err := s.State.AddRelation(eps...)
  1092  	c.Assert(err, jc.ErrorIsNil)
  1093  
  1094  	// If a unit agent is gone or down for some reason, a unit might
  1095  	// not leave the relation scope when the relation goes to
  1096  	// dying. If the destroy is forced, we shouldn't wait indefinitely
  1097  	// for that unit to leave scope.
  1098  	addRelationUnit := func(c *gc.C, app *state.Application) *state.RelationUnit {
  1099  		unit, err := app.AddUnit(state.AddUnitParams{})
  1100  		c.Assert(err, jc.ErrorIsNil)
  1101  		machine := s.Factory.MakeMachine(c, &factory.MachineParams{})
  1102  		err = unit.AssignToMachine(machine)
  1103  		c.Assert(err, jc.ErrorIsNil)
  1104  		relUnit, err := rel.Unit(unit)
  1105  		c.Assert(err, jc.ErrorIsNil)
  1106  		err = relUnit.EnterScope(nil)
  1107  		c.Assert(err, jc.ErrorIsNil)
  1108  		return relUnit
  1109  	}
  1110  
  1111  	relUnits := []*state.RelationUnit{
  1112  		addRelationUnit(c, wordpress),
  1113  		addRelationUnit(c, wordpress),
  1114  		addRelationUnit(c, mysql),
  1115  	}
  1116  	// Destroy one of the units to be sure the cleanup isn't
  1117  	// retrieving it.
  1118  	unit, err := s.State.Unit("mysql/0")
  1119  	c.Assert(err, jc.ErrorIsNil)
  1120  	err = unit.Destroy()
  1121  	c.Assert(err, jc.ErrorIsNil)
  1122  	err = unit.Refresh()
  1123  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1124  
  1125  	s.assertRelationCleanedUp(c, rel, relUnits)
  1126  }
  1127  
  1128  func (s *RelationSuite) TestDestroyForceStuckSubordinateUnits(c *gc.C) {
  1129  	prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeContainer)
  1130  	prr.allEnterScope(c)
  1131  
  1132  	rel := prr.rel
  1133  	relUnits := []*state.RelationUnit{
  1134  		prr.pru0, prr.pru1, prr.rru0, prr.rru1,
  1135  	}
  1136  	s.assertRelationCleanedUp(c, rel, relUnits)
  1137  }
  1138  
  1139  func (s *RelationSuite) TestDestroyForceStuckRemoteUnits(c *gc.C) {
  1140  	mysqlEps := []charm.Relation{
  1141  		{
  1142  			Interface: "mysql",
  1143  			Name:      "db",
  1144  			Role:      charm.RoleProvider,
  1145  			Scope:     charm.ScopeGlobal,
  1146  		},
  1147  	}
  1148  	_, err := s.State.AddRemoteApplication(state.AddRemoteApplicationParams{
  1149  		Name:        "mysql",
  1150  		SourceModel: s.Model.ModelTag(),
  1151  		Token:       "t0",
  1152  		Endpoints:   mysqlEps,
  1153  	})
  1154  	c.Assert(err, jc.ErrorIsNil)
  1155  
  1156  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1157  	eps, err := s.State.InferEndpoints("wordpress", "mysql")
  1158  	c.Assert(err, jc.ErrorIsNil)
  1159  	rel, err := s.State.AddRelation(eps...)
  1160  	c.Assert(err, jc.ErrorIsNil)
  1161  
  1162  	unit, err := wordpress.AddUnit(state.AddUnitParams{})
  1163  	c.Assert(err, jc.ErrorIsNil)
  1164  	machine := s.Factory.MakeMachine(c, &factory.MachineParams{})
  1165  	err = unit.AssignToMachine(machine)
  1166  	c.Assert(err, jc.ErrorIsNil)
  1167  	localRelUnit, err := rel.Unit(unit)
  1168  	c.Assert(err, jc.ErrorIsNil)
  1169  	err = localRelUnit.EnterScope(nil)
  1170  	c.Assert(err, jc.ErrorIsNil)
  1171  
  1172  	remoteRelUnit, err := rel.RemoteUnit("mysql/0")
  1173  	c.Assert(err, jc.ErrorIsNil)
  1174  	err = remoteRelUnit.EnterScope(nil)
  1175  	c.Assert(err, jc.ErrorIsNil)
  1176  
  1177  	opErrs, err := rel.DestroyWithForce(true, time.Minute)
  1178  	c.Assert(opErrs, gc.HasLen, 0)
  1179  	c.Assert(err, jc.ErrorIsNil)
  1180  
  1181  	err = rel.Refresh()
  1182  	c.Assert(err, jc.ErrorIsNil)
  1183  	c.Assert(rel.Life(), gc.Equals, state.Dying)
  1184  
  1185  	// Schedules a cleanup to remove the unit scope if needed.
  1186  	s.assertNeedsCleanup(c)
  1187  
  1188  	// But running cleanup immediately doesn't do it all.
  1189  	err = s.State.Cleanup()
  1190  	c.Assert(err, jc.ErrorIsNil)
  1191  	s.assertNeedsCleanup(c)
  1192  
  1193  	// Remote units forced out of scope.
  1194  	assertInScope(c, localRelUnit)
  1195  	assertNotInScope(c, remoteRelUnit)
  1196  
  1197  	err = rel.Refresh()
  1198  	c.Assert(err, jc.ErrorIsNil)
  1199  	c.Assert(rel.Life(), gc.Equals, state.Dying)
  1200  
  1201  	s.Clock.Advance(time.Minute)
  1202  
  1203  	err = s.State.Cleanup()
  1204  	c.Assert(err, jc.ErrorIsNil)
  1205  
  1206  	assertNotInScope(c, localRelUnit)
  1207  
  1208  	err = rel.Refresh()
  1209  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1210  }
  1211  
  1212  func (s *RelationSuite) TestDestroyForceIsFineIfUnitsAlreadyLeft(c *gc.C) {
  1213  	prr := newProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal)
  1214  	prr.allEnterScope(c)
  1215  	rel := prr.rel
  1216  	relUnits := []*state.RelationUnit{
  1217  		prr.pru0, prr.pru1, prr.rru0, prr.rru1,
  1218  	}
  1219  	opErrs, err := rel.DestroyWithForce(true, time.Minute)
  1220  	c.Assert(opErrs, gc.HasLen, 0)
  1221  	c.Assert(err, jc.ErrorIsNil)
  1222  
  1223  	err = rel.Refresh()
  1224  	c.Assert(err, jc.ErrorIsNil)
  1225  	c.Assert(rel.Life(), gc.Equals, state.Dying)
  1226  
  1227  	// Schedules a cleanup to remove the unit scope if needed.
  1228  	s.assertNeedsCleanup(c)
  1229  
  1230  	// But running cleanup immediately doesn't do it.
  1231  	err = s.State.Cleanup()
  1232  	c.Assert(err, jc.ErrorIsNil)
  1233  	s.assertNeedsCleanup(c)
  1234  	for i, ru := range relUnits {
  1235  		c.Logf("%d", i)
  1236  		assertJoined(c, ru)
  1237  	}
  1238  	err = rel.Refresh()
  1239  	c.Assert(err, jc.ErrorIsNil)
  1240  	c.Assert(rel.Life(), gc.Equals, state.Dying)
  1241  
  1242  	// In the meantime the units correctly leave scope.
  1243  	s.Clock.Advance(30 * time.Second)
  1244  
  1245  	for i, ru := range relUnits {
  1246  		c.Logf("%d", i)
  1247  		err := ru.LeaveScope()
  1248  		c.Assert(err, jc.ErrorIsNil)
  1249  	}
  1250  	err = rel.Refresh()
  1251  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1252  	s.Clock.Advance(30 * time.Second)
  1253  
  1254  	err = s.State.Cleanup()
  1255  	c.Assert(err, jc.ErrorIsNil)
  1256  
  1257  	// If the cleanup had failed because the relation had gone, it
  1258  	// would be left in the collection.
  1259  	s.assertNoCleanups(c)
  1260  }
  1261  
  1262  func (s *RelationSuite) assertRelationCleanedUp(c *gc.C, rel *state.Relation, relUnits []*state.RelationUnit) {
  1263  	opErrs, err := rel.DestroyWithForce(true, time.Minute)
  1264  	c.Assert(opErrs, gc.HasLen, 0)
  1265  	c.Assert(err, jc.ErrorIsNil)
  1266  
  1267  	err = rel.Refresh()
  1268  	c.Assert(err, jc.ErrorIsNil)
  1269  	c.Assert(rel.Life(), gc.Equals, state.Dying)
  1270  
  1271  	// Schedules a cleanup to remove the unit scope if needed.
  1272  	s.assertNeedsCleanup(c)
  1273  
  1274  	// But running cleanup immediately doesn't do it.
  1275  	err = s.State.Cleanup()
  1276  	c.Assert(err, jc.ErrorIsNil)
  1277  	s.assertNeedsCleanup(c)
  1278  	for i, ru := range relUnits {
  1279  		c.Logf("%d", i)
  1280  		assertJoined(c, ru)
  1281  	}
  1282  	err = rel.Refresh()
  1283  	c.Assert(err, jc.ErrorIsNil)
  1284  	c.Assert(rel.Life(), gc.Equals, state.Dying)
  1285  
  1286  	s.Clock.Advance(time.Minute)
  1287  
  1288  	err = s.State.Cleanup()
  1289  	c.Assert(err, jc.ErrorIsNil)
  1290  
  1291  	for i, ru := range relUnits {
  1292  		c.Logf("%d", i)
  1293  		assertNotInScope(c, ru)
  1294  	}
  1295  
  1296  	err = rel.Refresh()
  1297  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1298  }
  1299  
  1300  func (s *RelationSuite) assertNeedsCleanup(c *gc.C) {
  1301  	dirty, err := s.State.NeedsCleanup()
  1302  	c.Assert(err, jc.ErrorIsNil)
  1303  	c.Assert(dirty, jc.IsTrue)
  1304  }
  1305  
  1306  func (s *RelationSuite) assertNoCleanups(c *gc.C) {
  1307  	dirty, err := s.State.NeedsCleanup()
  1308  	c.Assert(err, jc.ErrorIsNil)
  1309  	c.Assert(dirty, jc.IsFalse)
  1310  }