github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.v4/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.State
    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.NewAddress(
    86  		strings.Replace(name, "/", "-", 1)+".testing.invalid", network.ScopeCloudLocal)
    87  	err = machine.SetAddresses(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 := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
    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 := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
   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(worstCase):
   170  		c.Fatalf("timed out waiting for absence detection")
   171  	}
   172  }
   173  
   174  func (s *RelationerSuite) TestStartStopHooks(c *gc.C) {
   175  	ru1, _ := s.AddRelationUnit(c, "u/1")
   176  	ru2, _ := s.AddRelationUnit(c, "u/2")
   177  	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
   178  	c.Assert(r.IsImplicit(), jc.IsFalse)
   179  	err := r.Join()
   180  	c.Assert(err, jc.ErrorIsNil)
   181  
   182  	// Check no hooks are being sent.
   183  	s.assertNoHook(c)
   184  
   185  	// Start hooks, and check that still no changes are sent.
   186  	r.StartHooks()
   187  	defer stopHooks(c, r)
   188  	s.assertNoHook(c)
   189  
   190  	// Check we can't start hooks again.
   191  	f := func() { r.StartHooks() }
   192  	c.Assert(f, gc.PanicMatches, "hooks already started!")
   193  
   194  	// Join u/1 to the relation, and check that we receive the expected hooks.
   195  	settings := map[string]interface{}{"unit": "settings"}
   196  	err = ru1.EnterScope(settings)
   197  	c.Assert(err, jc.ErrorIsNil)
   198  	s.assertHook(c, hook.Info{
   199  		Kind:       hooks.RelationJoined,
   200  		RemoteUnit: "u/1",
   201  	})
   202  	s.assertHook(c, hook.Info{
   203  		Kind:       hooks.RelationChanged,
   204  		RemoteUnit: "u/1",
   205  	})
   206  	s.assertNoHook(c)
   207  
   208  	// Stop hooks, make more changes, check no events.
   209  	err = r.StopHooks()
   210  	c.Assert(err, jc.ErrorIsNil)
   211  	err = ru1.LeaveScope()
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	err = ru2.EnterScope(nil)
   214  	c.Assert(err, jc.ErrorIsNil)
   215  	node, err := ru2.Settings()
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	node.Set("private-address", "roehampton")
   218  	_, err = node.Write()
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	s.assertNoHook(c)
   221  
   222  	// Stop hooks again to verify safety.
   223  	err = r.StopHooks()
   224  	c.Assert(err, jc.ErrorIsNil)
   225  	s.assertNoHook(c)
   226  
   227  	// Start them again, and check we get the expected events sent.
   228  	r.StartHooks()
   229  	defer stopHooks(c, r)
   230  	s.assertHook(c, hook.Info{
   231  		Kind:       hooks.RelationDeparted,
   232  		RemoteUnit: "u/1",
   233  	})
   234  	s.assertHook(c, hook.Info{
   235  		Kind:          hooks.RelationJoined,
   236  		ChangeVersion: 1,
   237  		RemoteUnit:    "u/2",
   238  	})
   239  	s.assertHook(c, hook.Info{
   240  		Kind:          hooks.RelationChanged,
   241  		ChangeVersion: 1,
   242  		RemoteUnit:    "u/2",
   243  	})
   244  	s.assertNoHook(c)
   245  
   246  	// Stop them again, just to be sure.
   247  	err = r.StopHooks()
   248  	c.Assert(err, jc.ErrorIsNil)
   249  	s.assertNoHook(c)
   250  }
   251  
   252  func (s *RelationerSuite) TestPrepareCommitHooks(c *gc.C) {
   253  	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
   254  	err := r.Join()
   255  	c.Assert(err, jc.ErrorIsNil)
   256  
   257  	assertMembers := func(expect map[string]int64) {
   258  		c.Assert(s.dir.State().Members, jc.DeepEquals, expect)
   259  		expectNames := make([]string, 0, len(expect))
   260  		for name := range expect {
   261  			expectNames = append(expectNames, name)
   262  		}
   263  		c.Assert(r.ContextInfo().MemberNames, jc.SameContents, expectNames)
   264  	}
   265  	assertMembers(map[string]int64{})
   266  
   267  	// Check preparing an invalid hook changes nothing.
   268  	changed := hook.Info{
   269  		Kind:          hooks.RelationChanged,
   270  		RemoteUnit:    "u/1",
   271  		ChangeVersion: 7,
   272  	}
   273  	_, err = r.PrepareHook(changed)
   274  	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
   275  	assertMembers(map[string]int64{})
   276  
   277  	// Check preparing a valid hook updates neither the context nor persistent
   278  	// relation state.
   279  	joined := hook.Info{
   280  		Kind:       hooks.RelationJoined,
   281  		RemoteUnit: "u/1",
   282  	}
   283  	name, err := r.PrepareHook(joined)
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	c.Assert(name, gc.Equals, "ring-relation-joined")
   286  	assertMembers(map[string]int64{})
   287  
   288  	// Check that preparing the following hook fails as before...
   289  	_, err = r.PrepareHook(changed)
   290  	c.Assert(err, gc.ErrorMatches, `inappropriate "relation-changed" for "u/1": unit has not joined`)
   291  	assertMembers(map[string]int64{})
   292  
   293  	// ...but that committing the previous hook updates the persistent
   294  	// relation state...
   295  	err = r.CommitHook(joined)
   296  	c.Assert(err, jc.ErrorIsNil)
   297  	assertMembers(map[string]int64{"u/1": 0})
   298  
   299  	// ...and allows us to prepare the next hook...
   300  	name, err = r.PrepareHook(changed)
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	c.Assert(name, gc.Equals, "ring-relation-changed")
   303  	assertMembers(map[string]int64{"u/1": 0})
   304  
   305  	// ...and commit it.
   306  	err = r.CommitHook(changed)
   307  	c.Assert(err, jc.ErrorIsNil)
   308  	assertMembers(map[string]int64{"u/1": 7})
   309  
   310  	// To verify implied behaviour above, prepare a new joined hook with
   311  	// missing membership information, and check relation context
   312  	// membership is stil not updated...
   313  	joined.RemoteUnit = "u/2"
   314  	joined.ChangeVersion = 3
   315  	name, err = r.PrepareHook(joined)
   316  	c.Assert(err, jc.ErrorIsNil)
   317  	c.Assert(name, gc.Equals, "ring-relation-joined")
   318  	assertMembers(map[string]int64{"u/1": 7})
   319  
   320  	// ...until commit, at which point so is relation state.
   321  	err = r.CommitHook(joined)
   322  	c.Assert(err, jc.ErrorIsNil)
   323  	assertMembers(map[string]int64{"u/1": 7, "u/2": 3})
   324  }
   325  
   326  func (s *RelationerSuite) TestSetDying(c *gc.C) {
   327  	ru1, _ := s.AddRelationUnit(c, "u/1")
   328  	settings := map[string]interface{}{"unit": "settings"}
   329  	err := ru1.EnterScope(settings)
   330  	c.Assert(err, jc.ErrorIsNil)
   331  	r := uniter.NewRelationer(s.apiRelUnit, s.dir, s.hooks)
   332  	err = r.Join()
   333  	c.Assert(err, jc.ErrorIsNil)
   334  	r.StartHooks()
   335  	defer stopHooks(c, r)
   336  	s.assertHook(c, hook.Info{
   337  		Kind:       hooks.RelationJoined,
   338  		RemoteUnit: "u/1",
   339  	})
   340  
   341  	// While a changed hook is still pending, the relation (or possibly the unit,
   342  	// pending lifecycle work), changes Life to Dying, and the relationer is
   343  	// informed.
   344  	err = r.SetDying()
   345  	c.Assert(err, jc.ErrorIsNil)
   346  
   347  	// Check that we cannot rejoin the relation.
   348  	f := func() { r.Join() }
   349  	c.Assert(f, gc.PanicMatches, "dying relationer must not join!")
   350  
   351  	// ...but the hook stream continues, sending the required changed hook for
   352  	// u/1 before moving on to a departed, despite the fact that its pinger is
   353  	// still running, and closing with a broken.
   354  	s.assertHook(c, hook.Info{Kind: hooks.RelationChanged, RemoteUnit: "u/1"})
   355  	s.assertHook(c, hook.Info{Kind: hooks.RelationDeparted, RemoteUnit: "u/1"})
   356  	s.assertHook(c, hook.Info{Kind: hooks.RelationBroken})
   357  
   358  	// Check that the relation state has been broken.
   359  	err = s.dir.State().Validate(hook.Info{Kind: hooks.RelationBroken})
   360  	c.Assert(err, gc.ErrorMatches, ".*: relation is broken and cannot be changed further")
   361  }
   362  
   363  func (s *RelationerSuite) assertNoHook(c *gc.C) {
   364  	s.BackingState.StartSync()
   365  	select {
   366  	case hi, ok := <-s.hooks:
   367  		c.Fatalf("got unexpected hook info %#v (%t)", hi, ok)
   368  	case <-time.After(coretesting.ShortWait):
   369  	}
   370  }
   371  
   372  func (s *RelationerSuite) assertHook(c *gc.C, expect hook.Info) {
   373  	s.BackingState.StartSync()
   374  	// We must ensure the local state dir exists first.
   375  	c.Assert(s.dir.Ensure(), gc.IsNil)
   376  	select {
   377  	case hi, ok := <-s.hooks:
   378  		c.Assert(ok, jc.IsTrue)
   379  		expect.ChangeVersion = hi.ChangeVersion
   380  		c.Assert(hi, gc.DeepEquals, expect)
   381  		c.Assert(s.dir.Write(hi), gc.Equals, nil)
   382  	case <-time.After(coretesting.LongWait):
   383  		c.Fatalf("timed out waiting for %#v", expect)
   384  	}
   385  }
   386  
   387  type stopper interface {
   388  	Stop() error
   389  }
   390  
   391  func stop(c *gc.C, s stopper) {
   392  	c.Assert(s.Stop(), gc.IsNil)
   393  }
   394  
   395  func stopHooks(c *gc.C, r *uniter.Relationer) {
   396  	c.Assert(r.StopHooks(), gc.IsNil)
   397  }
   398  
   399  type RelationerImplicitSuite struct {
   400  	jujutesting.JujuConnSuite
   401  }
   402  
   403  var _ = gc.Suite(&RelationerImplicitSuite{})
   404  
   405  func (s *RelationerImplicitSuite) TestImplicitRelationer(c *gc.C) {
   406  	// Create a relationer for an implicit endpoint (mysql:juju-info).
   407  	mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
   408  	u, err := mysql.AddUnit()
   409  	c.Assert(err, jc.ErrorIsNil)
   410  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   411  	c.Assert(err, jc.ErrorIsNil)
   412  	err = u.AssignToMachine(machine)
   413  	c.Assert(err, jc.ErrorIsNil)
   414  	err = machine.SetAddresses(network.NewAddress("blah", network.ScopeCloudLocal))
   415  	c.Assert(err, jc.ErrorIsNil)
   416  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
   417  	eps, err := s.State.InferEndpoints("logging", "mysql")
   418  	c.Assert(err, jc.ErrorIsNil)
   419  	rel, err := s.State.AddRelation(eps...)
   420  	c.Assert(err, jc.ErrorIsNil)
   421  	relsDir := c.MkDir()
   422  	dir, err := relation.ReadStateDir(relsDir, rel.Id())
   423  	c.Assert(err, jc.ErrorIsNil)
   424  	hooks := make(chan hook.Info)
   425  
   426  	password, err := utils.RandomPassword()
   427  	c.Assert(err, jc.ErrorIsNil)
   428  	err = u.SetPassword(password)
   429  	c.Assert(err, jc.ErrorIsNil)
   430  	st := s.OpenAPIAs(c, u.Tag(), password)
   431  	uniterState, err := st.Uniter()
   432  	c.Assert(err, jc.ErrorIsNil)
   433  	c.Assert(uniterState, gc.NotNil)
   434  
   435  	apiUnit, err := uniterState.Unit(u.Tag().(names.UnitTag))
   436  	c.Assert(err, jc.ErrorIsNil)
   437  	apiRel, err := uniterState.Relation(rel.Tag().(names.RelationTag))
   438  	c.Assert(err, jc.ErrorIsNil)
   439  	apiRelUnit, err := apiRel.Unit(apiUnit)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  
   442  	r := uniter.NewRelationer(apiRelUnit, dir, hooks)
   443  	c.Assert(r, jc.Satisfies, (*uniter.Relationer).IsImplicit)
   444  
   445  	// Join the relation.
   446  	err = r.Join()
   447  	c.Assert(err, jc.ErrorIsNil)
   448  	sub, err := s.State.Unit("logging/0")
   449  	c.Assert(err, jc.ErrorIsNil)
   450  
   451  	// Join the other side; check no hooks are sent.
   452  	r.StartHooks()
   453  	defer func() { c.Assert(r.StopHooks(), gc.IsNil) }()
   454  	subru, err := rel.Unit(sub)
   455  	c.Assert(err, jc.ErrorIsNil)
   456  	err = subru.EnterScope(map[string]interface{}{"some": "data"})
   457  	c.Assert(err, jc.ErrorIsNil)
   458  	s.State.StartSync()
   459  	select {
   460  	case <-time.After(coretesting.ShortWait):
   461  	case <-hooks:
   462  		c.Fatalf("unexpected hook generated")
   463  	}
   464  
   465  	// Set it to Dying; check that the dir is removed immediately.
   466  	err = r.SetDying()
   467  	c.Assert(err, jc.ErrorIsNil)
   468  	path := strconv.Itoa(rel.Id())
   469  	ft.Removed{path}.Check(c, relsDir)
   470  
   471  	// Check that it left scope, by leaving scope on the other side and destroying
   472  	// the relation.
   473  	err = subru.LeaveScope()
   474  	c.Assert(err, jc.ErrorIsNil)
   475  	err = rel.Destroy()
   476  	c.Assert(err, jc.ErrorIsNil)
   477  	err = rel.Refresh()
   478  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   479  
   480  	// Verify that no other hooks were sent at any stage.
   481  	select {
   482  	case <-hooks:
   483  		c.Fatalf("unexpected hook generated")
   484  	default:
   485  	}
   486  }