github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/relationer_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter_test
     5  
     6  import (
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	jc "github.com/juju/testing/checkers"
    14  	ft "github.com/juju/testing/filetesting"
    15  	"github.com/juju/utils"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/juju/charm.v5/hooks"
    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"
    26  	"github.com/juju/juju/worker/uniter/hook"
    27  	"github.com/juju/juju/worker/uniter/relation"
    28  )
    29  
    30  type RelationerSuite struct {
    31  	jujutesting.JujuConnSuite
    32  	hooks   chan hook.Info
    33  	svc     *state.Service
    34  	rel     *state.Relation
    35  	dir     *relation.StateDir
    36  	dirPath string
    37  
    38  	st         api.Connection
    39  	uniter     *apiuniter.State
    40  	apiRelUnit *apiuniter.RelationUnit
    41  }
    42  
    43  var _ = gc.Suite(&RelationerSuite{})
    44  
    45  func (s *RelationerSuite) SetUpTest(c *gc.C) {
    46  	s.JujuConnSuite.SetUpTest(c)
    47  	var err error
    48  	s.svc = s.AddTestingService(c, "u", s.AddTestingCharm(c, "riak"))
    49  	c.Assert(err, jc.ErrorIsNil)
    50  	rels, err := s.svc.Relations()
    51  	c.Assert(err, jc.ErrorIsNil)
    52  	c.Assert(rels, gc.HasLen, 1)
    53  	s.rel = rels[0]
    54  	_, unit := s.AddRelationUnit(c, "u/0")
    55  	s.dirPath = c.MkDir()
    56  	s.dir, err = relation.ReadStateDir(s.dirPath, s.rel.Id())
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	s.hooks = make(chan hook.Info)
    59  
    60  	password, err := utils.RandomPassword()
    61  	c.Assert(err, jc.ErrorIsNil)
    62  	err = unit.SetPassword(password)
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	s.st = s.OpenAPIAs(c, unit.Tag(), password)
    65  	s.uniter, err = s.st.Uniter()
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	c.Assert(s.uniter, gc.NotNil)
    68  
    69  	apiUnit, err := s.uniter.Unit(unit.Tag().(names.UnitTag))
    70  	c.Assert(err, jc.ErrorIsNil)
    71  	apiRel, err := s.uniter.Relation(s.rel.Tag().(names.RelationTag))
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	s.apiRelUnit, err = apiRel.Unit(apiUnit)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  }
    76  
    77  func (s *RelationerSuite) AddRelationUnit(c *gc.C, name string) (*state.RelationUnit, *state.Unit) {
    78  	u, err := s.svc.AddUnit()
    79  	c.Assert(err, jc.ErrorIsNil)
    80  	c.Assert(u.Name(), gc.Equals, name)
    81  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
    82  	c.Assert(err, jc.ErrorIsNil)
    83  	err = u.AssignToMachine(machine)
    84  	c.Assert(err, jc.ErrorIsNil)
    85  	privateAddr := network.NewScopedAddress(
    86  		strings.Replace(name, "/", "-", 1)+".testing.invalid", network.ScopeCloudLocal,
    87  	)
    88  	err = machine.SetProviderAddresses(privateAddr)
    89  	c.Assert(err, jc.ErrorIsNil)
    90  	ru, err := s.rel.Unit(u)
    91  	c.Assert(err, jc.ErrorIsNil)
    92  	return ru, u
    93  }
    94  
    95  func (s *RelationerSuite) TestStateDir(c *gc.C) {
    96  	// Create the relationer; check its state dir is not created.
    97  	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
    98  	path := strconv.Itoa(s.rel.Id())
    99  	ft.Removed{path}.Check(c, s.dirPath)
   100  
   101  	// Join the relation; check the dir was created.
   102  	err := r.Join()
   103  	c.Assert(err, jc.ErrorIsNil)
   104  	ft.Dir{path, 0755}.Check(c, s.dirPath)
   105  
   106  	// Prepare to depart the relation; check the dir is still there.
   107  	hi := hook.Info{Kind: hooks.RelationBroken}
   108  	_, err = r.PrepareHook(hi)
   109  	c.Assert(err, jc.ErrorIsNil)
   110  	ft.Dir{path, 0755}.Check(c, s.dirPath)
   111  
   112  	// Actually depart it; check the dir is removed.
   113  	err = r.CommitHook(hi)
   114  	c.Assert(err, jc.ErrorIsNil)
   115  	ft.Removed{path}.Check(c, s.dirPath)
   116  }
   117  
   118  func (s *RelationerSuite) TestEnterLeaveScope(c *gc.C) {
   119  	ru1, _ := s.AddRelationUnit(c, "u/1")
   120  	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
   121  
   122  	// u/1 does not consider u/0 to be alive.
   123  	w := ru1.Watch()
   124  	defer stop(c, w)
   125  	s.State.StartSync()
   126  	ch, ok := <-w.Changes()
   127  	c.Assert(ok, jc.IsTrue)
   128  	c.Assert(ch.Changed, gc.HasLen, 0)
   129  	c.Assert(ch.Departed, gc.HasLen, 0)
   130  
   131  	// u/0 enters scope; u/1 observes it.
   132  	err := r.Join()
   133  	c.Assert(err, jc.ErrorIsNil)
   134  	s.State.StartSync()
   135  	select {
   136  	case ch, ok := <-w.Changes():
   137  		c.Assert(ok, jc.IsTrue)
   138  		c.Assert(ch.Changed, gc.HasLen, 1)
   139  		_, found := ch.Changed["u/0"]
   140  		c.Assert(found, jc.IsTrue)
   141  		c.Assert(ch.Departed, gc.HasLen, 0)
   142  	case <-time.After(coretesting.LongWait):
   143  		c.Fatalf("timed out waiting for presence detection")
   144  	}
   145  
   146  	// re-Join is no-op.
   147  	err = r.Join()
   148  	c.Assert(err, jc.ErrorIsNil)
   149  	// TODO(jam): This would be a great to replace with statetesting.NotifyWatcherC
   150  	s.State.StartSync()
   151  	select {
   152  	case ch, ok := <-w.Changes():
   153  		c.Fatalf("got unexpected change: %#v, %#v", ch, ok)
   154  	case <-time.After(coretesting.ShortWait):
   155  	}
   156  
   157  	// u/0 leaves scope; u/1 observes it.
   158  	hi := hook.Info{Kind: hooks.RelationBroken}
   159  	_, err = r.PrepareHook(hi)
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	err = r.CommitHook(hi)
   163  	c.Assert(err, jc.ErrorIsNil)
   164  	s.State.StartSync()
   165  	select {
   166  	case ch, ok := <-w.Changes():
   167  		c.Assert(ok, jc.IsTrue)
   168  		c.Assert(ch.Changed, gc.HasLen, 0)
   169  		c.Assert(ch.Departed, gc.DeepEquals, []string{"u/0"})
   170  	case <-time.After(worstCase):
   171  		c.Fatalf("timed out waiting for absence detection")
   172  	}
   173  }
   174  
   175  func (s *RelationerSuite) TestStartStopHooks(c *gc.C) {
   176  	ru1, _ := s.AddRelationUnit(c, "u/1")
   177  	ru2, _ := s.AddRelationUnit(c, "u/2")
   178  	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
   179  	c.Assert(r.IsImplicit(), jc.IsFalse)
   180  	err := r.Join()
   181  	c.Assert(err, jc.ErrorIsNil)
   182  
   183  	// Check no hooks are being sent.
   184  	s.assertNoHook(c)
   185  
   186  	// Start hooks, and check that still no changes are sent.
   187  	r.StartHooks()
   188  	defer stopHooks(c, r)
   189  	s.assertNoHook(c)
   190  
   191  	// Check we can't start hooks again.
   192  	f := func() { r.StartHooks() }
   193  	c.Assert(f, gc.PanicMatches, "hooks already started!")
   194  
   195  	// Join u/1 to the relation, and check that we receive the expected hooks.
   196  	settings := map[string]interface{}{"unit": "settings"}
   197  	err = ru1.EnterScope(settings)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	s.assertHook(c, hook.Info{
   200  		Kind:       hooks.RelationJoined,
   201  		RemoteUnit: "u/1",
   202  	})
   203  	s.assertHook(c, hook.Info{
   204  		Kind:       hooks.RelationChanged,
   205  		RemoteUnit: "u/1",
   206  	})
   207  	s.assertNoHook(c)
   208  
   209  	// Stop hooks, make more changes, check no events.
   210  	err = r.StopHooks()
   211  	c.Assert(err, jc.ErrorIsNil)
   212  	err = ru1.LeaveScope()
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	err = ru2.EnterScope(nil)
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	node, err := ru2.Settings()
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	node.Set("private-address", "roehampton")
   219  	_, err = node.Write()
   220  	c.Assert(err, jc.ErrorIsNil)
   221  	s.assertNoHook(c)
   222  
   223  	// Stop hooks again to verify safety.
   224  	err = r.StopHooks()
   225  	c.Assert(err, jc.ErrorIsNil)
   226  	s.assertNoHook(c)
   227  
   228  	// Start them again, and check we get the expected events sent.
   229  	r.StartHooks()
   230  	defer stopHooks(c, r)
   231  	s.assertHook(c, hook.Info{
   232  		Kind:       hooks.RelationDeparted,
   233  		RemoteUnit: "u/1",
   234  	})
   235  	s.assertHook(c, hook.Info{
   236  		Kind:          hooks.RelationJoined,
   237  		ChangeVersion: 1,
   238  		RemoteUnit:    "u/2",
   239  	})
   240  	s.assertHook(c, hook.Info{
   241  		Kind:          hooks.RelationChanged,
   242  		ChangeVersion: 1,
   243  		RemoteUnit:    "u/2",
   244  	})
   245  	s.assertNoHook(c)
   246  
   247  	// Stop them again, just to be sure.
   248  	err = r.StopHooks()
   249  	c.Assert(err, jc.ErrorIsNil)
   250  	s.assertNoHook(c)
   251  }
   252  
   253  func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) {
   254  	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
   255  	err := r.Join()
   256  	c.Assert(err, jc.ErrorIsNil)
   257  
   258  	assertMembers := func(expect map[string]int64) {
   259  		c.Assert(s.dir.State().Members, jc.DeepEquals, expect)
   260  		expectNames := make([]string, 0, len(expect))
   261  		for name := range expect {
   262  			expectNames = append(expectNames, name)
   263  		}
   264  		c.Assert(r.ContextInfo().MemberNames, jc.SameContents, expectNames)
   265  	}
   266  	assertMembers(map[string]int64{})
   267  
   268  	// Check preparing an invalid hook changes nothing.
   269  	changed := hook.Info{
   270  		Kind:          hooks.RelationChanged,
   271  		RemoteUnit:    "u/1",
   272  		ChangeVersion: 7,
   273  	}
   274  	_, err = r.PrepareHook(changed)
   275  	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
   276  	assertMembers(map[string]int64{})
   277  
   278  	// Check preparing a valid hook updates neither the context nor persistent
   279  	// relation state.
   280  	joined := hook.Info{
   281  		Kind:       hooks.RelationJoined,
   282  		RemoteUnit: "u/1",
   283  	}
   284  	name, err := r.PrepareHook(joined)
   285  	c.Assert(err, jc.ErrorIsNil)
   286  	c.Assert(name, gc.Equals, "ring-relation-joined")
   287  	assertMembers(map[string]int64{})
   288  
   289  	// Check that preparing the following hook fails as before...
   290  	_, err = r.PrepareHook(changed)
   291  	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
   292  	assertMembers(map[string]int64{})
   293  
   294  	// ...but that committing the previous hook updates the persistent
   295  	// relation state...
   296  	err = r.CommitHook(joined)
   297  	c.Assert(err, jc.ErrorIsNil)
   298  	assertMembers(map[string]int64{"u/1": 0})
   299  
   300  	// ...and allows us to prepare the next hook...
   301  	name, err = r.PrepareHook(changed)
   302  	c.Assert(err, jc.ErrorIsNil)
   303  	c.Assert(name, gc.Equals, "ring-relation-changed")
   304  	assertMembers(map[string]int64{"u/1": 0})
   305  
   306  	// ...and commit it.
   307  	err = r.CommitHook(changed)
   308  	c.Assert(err, jc.ErrorIsNil)
   309  	assertMembers(map[string]int64{"u/1": 7})
   310  
   311  	// To verify implied behaviour above, prepare a new joined hook with
   312  	// missing membership information, and check relation context
   313  	// membership is stil not updated...
   314  	joined.RemoteUnit = "u/2"
   315  	joined.ChangeVersion = 3
   316  	name, err = r.PrepareHook(joined)
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	c.Assert(name, gc.Equals, "ring-relation-joined")
   319  	assertMembers(map[string]int64{"u/1": 7})
   320  
   321  	// ...until commit, at which point so is relation state.
   322  	err = r.CommitHook(joined)
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	assertMembers(map[string]int64{"u/1": 7, "u/2": 3})
   325  }
   326  
   327  func (s *RelationerSuite) TestSetDying(c *gc.C) {
   328  	ru1, _ := s.AddRelationUnit(c, "u/1")
   329  	settings := map[string]interface{}{"unit": "settings"}
   330  	err := ru1.EnterScope(settings)
   331  	c.Assert(err, jc.ErrorIsNil)
   332  	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
   333  	err = r.Join()
   334  	c.Assert(err, jc.ErrorIsNil)
   335  	r.StartHooks()
   336  	defer stopHooks(c, r)
   337  	s.assertHook(c, hook.Info{
   338  		Kind:       hooks.RelationJoined,
   339  		RemoteUnit: "u/1",
   340  	})
   341  
   342  	// While a changed hook is still pending, the relation (or possibly the unit,
   343  	// pending lifecycle work), changes Life to Dying, and the relationer is
   344  	// informed.
   345  	err = r.SetDying()
   346  	c.Assert(err, jc.ErrorIsNil)
   347  
   348  	// Check that we cannot rejoin the relation.
   349  	f := func() { r.Join() }
   350  	c.Assert(f, gc.PanicMatches, "dying relationer must not join!")
   351  
   352  	// ...but the hook stream continues, sending the required changed hook for
   353  	// u/1 before moving on to a departed, despite the fact that its pinger is
   354  	// still running, and closing with a broken.
   355  	s.assertHook(c, hook.Info{Kind: hooks.RelationChanged, RemoteUnit: "u/1"})
   356  	s.assertHook(c, hook.Info{Kind: hooks.RelationDeparted, RemoteUnit: "u/1"})
   357  	s.assertHook(c, hook.Info{Kind: hooks.RelationBroken})
   358  
   359  	// Check that the relation state has been broken.
   360  	err = s.dir.State().Validate(hook.Info{Kind: hooks.RelationBroken})
   361  	c.Assert(err, gc.ErrorMatches, ".*: relation is broken and cannot be changed further")
   362  }
   363  
   364  func (s *RelationerSuite) assertNoHook(c *gc.C) {
   365  	s.BackingState.StartSync()
   366  	select {
   367  	case hi, ok := <-s.hooks:
   368  		c.Fatalf("got unexpected hook info %#v (%t)", hi, ok)
   369  	case <-time.After(coretesting.ShortWait):
   370  	}
   371  }
   372  
   373  func (s *RelationerSuite) assertHook(c *gc.C, expect hook.Info) {
   374  	s.BackingState.StartSync()
   375  	// We must ensure the local state dir exists first.
   376  	c.Assert(s.dir.Ensure(), gc.IsNil)
   377  	select {
   378  	case hi, ok := <-s.hooks:
   379  		c.Assert(ok, jc.IsTrue)
   380  		expect.ChangeVersion = hi.ChangeVersion
   381  		c.Assert(hi, gc.DeepEquals, expect)
   382  		c.Assert(s.dir.Write(hi), gc.Equals, nil)
   383  	case <-time.After(coretesting.LongWait):
   384  		c.Fatalf("timed out waiting for %#v", expect)
   385  	}
   386  }
   387  
   388  type stopper interface {
   389  	Stop() error
   390  }
   391  
   392  func stop(c *gc.C, s stopper) {
   393  	c.Assert(s.Stop(), gc.IsNil)
   394  }
   395  
   396  func stopHooks(c *gc.C, r *uniter.Relationer) {
   397  	c.Assert(r.StopHooks(), gc.IsNil)
   398  }
   399  
   400  type RelationerImplicitSuite struct {
   401  	jujutesting.JujuConnSuite
   402  }
   403  
   404  var _ = gc.Suite(&RelationerImplicitSuite{})
   405  
   406  func (s *RelationerImplicitSuite) TestImplicitRelationer(c *gc.C) {
   407  	// Create a relationer for an implicit endpoint (mysql:juju-info).
   408  	mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
   409  	u, err := mysql.AddUnit()
   410  	c.Assert(err, jc.ErrorIsNil)
   411  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   412  	c.Assert(err, jc.ErrorIsNil)
   413  	err = u.AssignToMachine(machine)
   414  	c.Assert(err, jc.ErrorIsNil)
   415  	err = machine.SetProviderAddresses(network.NewScopedAddress("blah", network.ScopeCloudLocal))
   416  	c.Assert(err, jc.ErrorIsNil)
   417  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
   418  	eps, err := s.State.InferEndpoints("logging", "mysql")
   419  	c.Assert(err, jc.ErrorIsNil)
   420  	rel, err := s.State.AddRelation(eps...)
   421  	c.Assert(err, jc.ErrorIsNil)
   422  	relsDir := c.MkDir()
   423  	dir, err := relation.ReadStateDir(relsDir, rel.Id())
   424  	c.Assert(err, jc.ErrorIsNil)
   425  	hooks := make(chan hook.Info)
   426  
   427  	password, err := utils.RandomPassword()
   428  	c.Assert(err, jc.ErrorIsNil)
   429  	err = u.SetPassword(password)
   430  	c.Assert(err, jc.ErrorIsNil)
   431  	st := s.OpenAPIAs(c, u.Tag(), password)
   432  	uniterState, err := st.Uniter()
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	c.Assert(uniterState, gc.NotNil)
   435  
   436  	apiUnit, err := uniterState.Unit(u.Tag().(names.UnitTag))
   437  	c.Assert(err, jc.ErrorIsNil)
   438  	apiRel, err := uniterState.Relation(rel.Tag().(names.RelationTag))
   439  	c.Assert(err, jc.ErrorIsNil)
   440  	apiRelUnit, err := apiRel.Unit(apiUnit)
   441  	c.Assert(err, jc.ErrorIsNil)
   442  
   443  	r := uniter.NewRelationer(apiRelUnit, dir, hooks)
   444  	c.Assert(r, jc.Satisfies, (*uniter.Relationer).IsImplicit)
   445  
   446  	// Join the relation.
   447  	err = r.Join()
   448  	c.Assert(err, jc.ErrorIsNil)
   449  	sub, err := s.State.Unit("logging/0")
   450  	c.Assert(err, jc.ErrorIsNil)
   451  
   452  	// Join the other side; check no hooks are sent.
   453  	r.StartHooks()
   454  	defer func() { c.Assert(r.StopHooks(), gc.IsNil) }()
   455  	subru, err := rel.Unit(sub)
   456  	c.Assert(err, jc.ErrorIsNil)
   457  	err = subru.EnterScope(map[string]interface{}{"some": "data"})
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	s.State.StartSync()
   460  	select {
   461  	case <-time.After(coretesting.ShortWait):
   462  	case <-hooks:
   463  		c.Fatalf("unexpected hook generated")
   464  	}
   465  
   466  	// Set it to Dying; check that the dir is removed immediately.
   467  	err = r.SetDying()
   468  	c.Assert(err, jc.ErrorIsNil)
   469  	path := strconv.Itoa(rel.Id())
   470  	ft.Removed{path}.Check(c, relsDir)
   471  
   472  	// Check that it left scope, by leaving scope on the other side and destroying
   473  	// the relation.
   474  	err = subru.LeaveScope()
   475  	c.Assert(err, jc.ErrorIsNil)
   476  	err = rel.Destroy()
   477  	c.Assert(err, jc.ErrorIsNil)
   478  	err = rel.Refresh()
   479  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   480  
   481  	// Verify that no other hooks were sent at any stage.
   482  	select {
   483  	case <-hooks:
   484  		c.Fatalf("unexpected hook generated")
   485  	default:
   486  	}
   487  }