github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/relation/relationer_test.go (about)

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package relation_test
     5  
     6  import (
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	ft "github.com/juju/testing/filetesting"
    14  	"github.com/juju/utils"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/charm.v6/hooks"
    17  	"gopkg.in/juju/names.v2"
    18  
    19  	"github.com/juju/juju/api"
    20  	apiuniter "github.com/juju/juju/api/uniter"
    21  	jujutesting "github.com/juju/juju/juju/testing"
    22  	"github.com/juju/juju/network"
    23  	"github.com/juju/juju/state"
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/worker/uniter/hook"
    26  	"github.com/juju/juju/worker/uniter/relation"
    27  )
    28  
    29  type RelationerSuite struct {
    30  	jujutesting.JujuConnSuite
    31  	hooks   chan hook.Info
    32  	app     *state.Application
    33  	rel     *state.Relation
    34  	dir     *relation.StateDir
    35  	dirPath string
    36  
    37  	st         api.Connection
    38  	uniter     *apiuniter.State
    39  	apiRelUnit *apiuniter.RelationUnit
    40  }
    41  
    42  var _ = gc.Suite(&RelationerSuite{})
    43  
    44  func (s *RelationerSuite) SetUpTest(c *gc.C) {
    45  	s.JujuConnSuite.SetUpTest(c)
    46  	var err error
    47  	s.app = s.AddTestingApplication(c, "u", s.AddTestingCharm(c, "riak"))
    48  	c.Assert(err, jc.ErrorIsNil)
    49  	rels, err := s.app.Relations()
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	c.Assert(rels, gc.HasLen, 1)
    52  	s.rel = rels[0]
    53  	_, unit := s.AddRelationUnit(c, "u/0")
    54  	s.dirPath = c.MkDir()
    55  	s.dir, err = relation.ReadStateDir(s.dirPath, s.rel.Id())
    56  	c.Assert(err, jc.ErrorIsNil)
    57  	s.hooks = make(chan hook.Info)
    58  
    59  	password, err := utils.RandomPassword()
    60  	c.Assert(err, jc.ErrorIsNil)
    61  	err = unit.SetPassword(password)
    62  	c.Assert(err, jc.ErrorIsNil)
    63  	s.st = s.OpenAPIAs(c, unit.Tag(), password)
    64  	s.uniter, err = s.st.Uniter()
    65  	c.Assert(err, jc.ErrorIsNil)
    66  	c.Assert(s.uniter, gc.NotNil)
    67  
    68  	apiUnit, err := s.uniter.Unit(unit.Tag().(names.UnitTag))
    69  	c.Assert(err, jc.ErrorIsNil)
    70  	apiRel, err := s.uniter.Relation(s.rel.Tag().(names.RelationTag))
    71  	c.Assert(err, jc.ErrorIsNil)
    72  	s.apiRelUnit, err = apiRel.Unit(apiUnit)
    73  	c.Assert(err, jc.ErrorIsNil)
    74  }
    75  
    76  func (s *RelationerSuite) AddRelationUnit(c *gc.C, name string) (*state.RelationUnit, *state.Unit) {
    77  	u, err := s.app.AddUnit(state.AddUnitParams{})
    78  	c.Assert(err, jc.ErrorIsNil)
    79  	c.Assert(u.Name(), gc.Equals, name)
    80  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	err = u.AssignToMachine(machine)
    83  	c.Assert(err, jc.ErrorIsNil)
    84  	privateAddr := network.NewScopedAddress(
    85  		strings.Replace(name, "/", "-", 1)+".testing.invalid", network.ScopeCloudLocal,
    86  	)
    87  	err = machine.SetProviderAddresses(privateAddr)
    88  	c.Assert(err, jc.ErrorIsNil)
    89  	ru, err := s.rel.Unit(u)
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	return ru, u
    92  }
    93  
    94  func (s *RelationerSuite) TestStateDir(c *gc.C) {
    95  	// Create the relationer; check its state dir is not created.
    96  	r := relation.NewRelationer(s.apiRelUnit, s.dir)
    97  	path := strconv.Itoa(s.rel.Id())
    98  	ft.Removed{path}.Check(c, s.dirPath)
    99  
   100  	// Join the relation; check the dir was created.
   101  	err := r.Join()
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	ft.Dir{path, 0755}.Check(c, s.dirPath)
   104  
   105  	// Prepare to depart the relation; check the dir is still there.
   106  	hi := hook.Info{Kind: hooks.RelationBroken}
   107  	_, err = r.PrepareHook(hi)
   108  	c.Assert(err, jc.ErrorIsNil)
   109  	ft.Dir{path, 0755}.Check(c, s.dirPath)
   110  
   111  	// Actually depart it; check the dir is removed.
   112  	err = r.CommitHook(hi)
   113  	c.Assert(err, jc.ErrorIsNil)
   114  	ft.Removed{path}.Check(c, s.dirPath)
   115  }
   116  
   117  func (s *RelationerSuite) TestEnterLeaveScope(c *gc.C) {
   118  	ru1, _ := s.AddRelationUnit(c, "u/1")
   119  	r := relation.NewRelationer(s.apiRelUnit, s.dir)
   120  
   121  	// u/1 does not consider u/0 to be alive.
   122  	w := ru1.Watch()
   123  	defer stop(c, w)
   124  	s.State.StartSync()
   125  	ch, ok := <-w.Changes()
   126  	c.Assert(ok, jc.IsTrue)
   127  	c.Assert(ch.Changed, gc.HasLen, 0)
   128  	c.Assert(ch.Departed, gc.HasLen, 0)
   129  
   130  	// u/0 enters scope; u/1 observes it.
   131  	err := r.Join()
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	s.State.StartSync()
   134  	select {
   135  	case ch, ok := <-w.Changes():
   136  		c.Assert(ok, jc.IsTrue)
   137  		c.Assert(ch.Changed, gc.HasLen, 1)
   138  		_, found := ch.Changed["u/0"]
   139  		c.Assert(found, jc.IsTrue)
   140  		c.Assert(ch.Departed, gc.HasLen, 0)
   141  	case <-time.After(coretesting.LongWait):
   142  		c.Fatalf("timed out waiting for presence detection")
   143  	}
   144  
   145  	// re-Join is no-op.
   146  	err = r.Join()
   147  	c.Assert(err, jc.ErrorIsNil)
   148  	// TODO(jam): This would be a great to replace with statetesting.NotifyWatcherC
   149  	s.State.StartSync()
   150  	select {
   151  	case ch, ok := <-w.Changes():
   152  		c.Fatalf("got unexpected change: %#v, %#v", ch, ok)
   153  	case <-time.After(coretesting.ShortWait):
   154  	}
   155  
   156  	// u/0 leaves scope; u/1 observes it.
   157  	hi := hook.Info{Kind: hooks.RelationBroken}
   158  	_, err = r.PrepareHook(hi)
   159  	c.Assert(err, jc.ErrorIsNil)
   160  
   161  	err = r.CommitHook(hi)
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	s.State.StartSync()
   164  	select {
   165  	case ch, ok := <-w.Changes():
   166  		c.Assert(ok, jc.IsTrue)
   167  		c.Assert(ch.Changed, gc.HasLen, 0)
   168  		c.Assert(ch.Departed, gc.DeepEquals, []string{"u/0"})
   169  	case <-time.After(coretesting.LongWait):
   170  		c.Fatalf("timed out waiting for absence detection")
   171  	}
   172  }
   173  
   174  func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) {
   175  	r := relation.NewRelationer(s.apiRelUnit, s.dir)
   176  	err := r.Join()
   177  	c.Assert(err, jc.ErrorIsNil)
   178  
   179  	assertMembers := func(expect map[string]int64) {
   180  		c.Assert(s.dir.State().Members, jc.DeepEquals, expect)
   181  		expectNames := make([]string, 0, len(expect))
   182  		for name := range expect {
   183  			expectNames = append(expectNames, name)
   184  		}
   185  		c.Assert(r.ContextInfo().MemberNames, jc.SameContents, expectNames)
   186  	}
   187  	assertMembers(map[string]int64{})
   188  
   189  	// Check preparing an invalid hook changes nothing.
   190  	changed := hook.Info{
   191  		Kind:          hooks.RelationChanged,
   192  		RemoteUnit:    "u/1",
   193  		ChangeVersion: 7,
   194  	}
   195  	_, err = r.PrepareHook(changed)
   196  	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
   197  	assertMembers(map[string]int64{})
   198  
   199  	// Check preparing a valid hook updates neither the context nor persistent
   200  	// relation state.
   201  	joined := hook.Info{
   202  		Kind:       hooks.RelationJoined,
   203  		RemoteUnit: "u/1",
   204  	}
   205  	name, err := r.PrepareHook(joined)
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	c.Assert(name, gc.Equals, "ring-relation-joined")
   208  	assertMembers(map[string]int64{})
   209  
   210  	// Check that preparing the following hook fails as before...
   211  	_, err = r.PrepareHook(changed)
   212  	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
   213  	assertMembers(map[string]int64{})
   214  
   215  	// ...but that committing the previous hook updates the persistent
   216  	// relation state...
   217  	err = r.CommitHook(joined)
   218  	c.Assert(err, jc.ErrorIsNil)
   219  	assertMembers(map[string]int64{"u/1": 0})
   220  
   221  	// ...and allows us to prepare the next hook...
   222  	name, err = r.PrepareHook(changed)
   223  	c.Assert(err, jc.ErrorIsNil)
   224  	c.Assert(name, gc.Equals, "ring-relation-changed")
   225  	assertMembers(map[string]int64{"u/1": 0})
   226  
   227  	// ...and commit it.
   228  	err = r.CommitHook(changed)
   229  	c.Assert(err, jc.ErrorIsNil)
   230  	assertMembers(map[string]int64{"u/1": 7})
   231  
   232  	// To verify implied behaviour above, prepare a new joined hook with
   233  	// missing membership information, and check relation context
   234  	// membership is stil not updated...
   235  	joined.RemoteUnit = "u/2"
   236  	joined.ChangeVersion = 3
   237  	name, err = r.PrepareHook(joined)
   238  	c.Assert(err, jc.ErrorIsNil)
   239  	c.Assert(name, gc.Equals, "ring-relation-joined")
   240  	assertMembers(map[string]int64{"u/1": 7})
   241  
   242  	// ...until commit, at which point so is relation state.
   243  	err = r.CommitHook(joined)
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	assertMembers(map[string]int64{"u/1": 7, "u/2": 3})
   246  }
   247  
   248  func (s *RelationerSuite) TestSetDying(c *gc.C) {
   249  	ru1, u := s.AddRelationUnit(c, "u/1")
   250  	settings := map[string]interface{}{"unit": "settings"}
   251  	err := ru1.EnterScope(settings)
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	r := relation.NewRelationer(s.apiRelUnit, s.dir)
   254  	err = r.Join()
   255  	c.Assert(err, jc.ErrorIsNil)
   256  
   257  	// Change Life to Dying check the results.
   258  	err = r.SetDying()
   259  	c.Assert(err, jc.ErrorIsNil)
   260  
   261  	// Check that we cannot rejoin the relation.
   262  	f := func() { r.Join() }
   263  	c.Assert(f, gc.PanicMatches, "dying relationer must not join!")
   264  
   265  	// Simulate a RelationBroken hook.
   266  	err = r.CommitHook(hook.Info{Kind: hooks.RelationBroken})
   267  	c.Assert(err, jc.ErrorIsNil)
   268  
   269  	// Check that the relation state has been broken.
   270  	err = s.dir.State().Validate(hook.Info{Kind: hooks.RelationBroken})
   271  	c.Assert(err, gc.ErrorMatches, ".*: relation is broken and cannot be changed further")
   272  
   273  	// Check that it left scope, by leaving scope on the other side and destroying
   274  	// the relation.
   275  	err = ru1.LeaveScope()
   276  	c.Assert(err, jc.ErrorIsNil)
   277  	err = u.Destroy()
   278  	c.Assert(err, jc.ErrorIsNil)
   279  	err = u.Refresh()
   280  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   281  }
   282  
   283  type stopper interface {
   284  	Stop() error
   285  }
   286  
   287  func stop(c *gc.C, s stopper) {
   288  	c.Assert(s.Stop(), gc.IsNil)
   289  }
   290  
   291  type RelationerImplicitSuite struct {
   292  	jujutesting.JujuConnSuite
   293  }
   294  
   295  var _ = gc.Suite(&RelationerImplicitSuite{})
   296  
   297  func (s *RelationerImplicitSuite) TestImplicitRelationer(c *gc.C) {
   298  	// Create a relationer for an implicit endpoint (mysql:juju-info).
   299  	mysql := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   300  	u, err := mysql.AddUnit(state.AddUnitParams{})
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   303  	c.Assert(err, jc.ErrorIsNil)
   304  	err = u.AssignToMachine(machine)
   305  	c.Assert(err, jc.ErrorIsNil)
   306  	err = machine.SetProviderAddresses(network.NewScopedAddress("blah", network.ScopeCloudLocal))
   307  	c.Assert(err, jc.ErrorIsNil)
   308  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
   309  	eps, err := s.State.InferEndpoints("logging", "mysql")
   310  	c.Assert(err, jc.ErrorIsNil)
   311  	rel, err := s.State.AddRelation(eps...)
   312  	c.Assert(err, jc.ErrorIsNil)
   313  	relsDir := c.MkDir()
   314  	dir, err := relation.ReadStateDir(relsDir, rel.Id())
   315  	c.Assert(err, jc.ErrorIsNil)
   316  
   317  	password, err := utils.RandomPassword()
   318  	c.Assert(err, jc.ErrorIsNil)
   319  	err = u.SetPassword(password)
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	st := s.OpenAPIAs(c, u.Tag(), password)
   322  	uniterState, err := st.Uniter()
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	c.Assert(uniterState, gc.NotNil)
   325  
   326  	apiUnit, err := uniterState.Unit(u.Tag().(names.UnitTag))
   327  	c.Assert(err, jc.ErrorIsNil)
   328  	apiRel, err := uniterState.Relation(rel.Tag().(names.RelationTag))
   329  	c.Assert(err, jc.ErrorIsNil)
   330  	apiRelUnit, err := apiRel.Unit(apiUnit)
   331  	c.Assert(err, jc.ErrorIsNil)
   332  
   333  	r := relation.NewRelationer(apiRelUnit, dir)
   334  	c.Assert(r, jc.Satisfies, (*relation.Relationer).IsImplicit)
   335  
   336  	// Hooks are not allowed.
   337  	f := func() { r.PrepareHook(hook.Info{}) }
   338  	c.Assert(f, gc.PanicMatches, "implicit relations must not run hooks")
   339  	f = func() { r.CommitHook(hook.Info{}) }
   340  	c.Assert(f, gc.PanicMatches, "implicit relations must not run hooks")
   341  
   342  	// Set it to Dying; check that the dir is removed immediately.
   343  	err = r.SetDying()
   344  	c.Assert(err, jc.ErrorIsNil)
   345  	path := strconv.Itoa(rel.Id())
   346  	ft.Removed{path}.Check(c, relsDir)
   347  
   348  	err = rel.Destroy()
   349  	c.Assert(err, jc.ErrorIsNil)
   350  	err = rel.Refresh()
   351  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   352  }