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