github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/uniter/runner/context/contextfactory_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package context_test
     5  
     6  import (
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils"
    15  	"github.com/juju/utils/fs"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/juju/charm.v6-unstable/hooks"
    18  
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/core/leadership"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/storage"
    23  	"github.com/juju/juju/testcharms"
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/worker/uniter/hook"
    26  	"github.com/juju/juju/worker/uniter/runner/context"
    27  	runnertesting "github.com/juju/juju/worker/uniter/runner/testing"
    28  )
    29  
    30  type ContextFactorySuite struct {
    31  	HookContextSuite
    32  	paths      runnertesting.RealPaths
    33  	factory    context.ContextFactory
    34  	membership map[int][]string
    35  }
    36  
    37  var _ = gc.Suite(&ContextFactorySuite{})
    38  
    39  func (s *ContextFactorySuite) SetUpTest(c *gc.C) {
    40  	s.HookContextSuite.SetUpTest(c)
    41  	s.paths = runnertesting.NewRealPaths(c)
    42  	s.membership = map[int][]string{}
    43  
    44  	contextFactory, err := context.NewContextFactory(
    45  		s.uniter,
    46  		s.unit.Tag().(names.UnitTag),
    47  		runnertesting.FakeTracker{},
    48  		s.getRelationInfos,
    49  		s.storage,
    50  		s.paths,
    51  		coretesting.NewClock(time.Time{}),
    52  	)
    53  	c.Assert(err, jc.ErrorIsNil)
    54  	s.factory = contextFactory
    55  }
    56  
    57  func (s *ContextFactorySuite) setUpCacheMethods(c *gc.C) {
    58  	// The factory's caches are created lazily, so it doesn't have any at all to
    59  	// begin with. Creating and discarding a context lets us call updateCache
    60  	// without panicking. (IMO this is less invasive that making updateCache
    61  	// responsible for creating missing caches etc.)
    62  	_, err := s.factory.HookContext(hook.Info{Kind: hooks.Install})
    63  	c.Assert(err, jc.ErrorIsNil)
    64  }
    65  
    66  func (s *ContextFactorySuite) updateCache(relId int, unitName string, settings params.Settings) {
    67  	context.UpdateCachedSettings(s.factory, relId, unitName, settings)
    68  }
    69  
    70  func (s *ContextFactorySuite) getCache(relId int, unitName string) (params.Settings, bool) {
    71  	return context.CachedSettings(s.factory, relId, unitName)
    72  }
    73  
    74  func (s *ContextFactorySuite) SetCharm(c *gc.C, name string) {
    75  	err := os.RemoveAll(s.paths.GetCharmDir())
    76  	c.Assert(err, jc.ErrorIsNil)
    77  	err = fs.Copy(testcharms.Repo.CharmDirPath(name), s.paths.GetCharmDir())
    78  	c.Assert(err, jc.ErrorIsNil)
    79  }
    80  
    81  func (s *ContextFactorySuite) getRelationInfos() map[int]*context.RelationInfo {
    82  	info := map[int]*context.RelationInfo{}
    83  	for relId, relUnit := range s.apiRelunits {
    84  		info[relId] = &context.RelationInfo{
    85  			RelationUnit: relUnit,
    86  			MemberNames:  s.membership[relId],
    87  		}
    88  	}
    89  	return info
    90  }
    91  
    92  func (s *ContextFactorySuite) testLeadershipContextWiring(c *gc.C, createContext func() *context.HookContext) {
    93  	var stub testing.Stub
    94  	stub.SetErrors(errors.New("bam"))
    95  	restore := context.PatchNewLeadershipContext(
    96  		func(accessor context.LeadershipSettingsAccessor, tracker leadership.Tracker) context.LeadershipContext {
    97  			stub.AddCall("NewLeadershipContext", accessor, tracker)
    98  			return &StubLeadershipContext{Stub: &stub}
    99  		},
   100  	)
   101  	defer restore()
   102  
   103  	ctx := createContext()
   104  	isLeader, err := ctx.IsLeader()
   105  	c.Check(err, gc.ErrorMatches, "bam")
   106  	c.Check(isLeader, jc.IsFalse)
   107  
   108  	stub.CheckCalls(c, []testing.StubCall{{
   109  		FuncName: "NewLeadershipContext",
   110  		Args:     []interface{}{s.uniter.LeadershipSettings, runnertesting.FakeTracker{}},
   111  	}, {
   112  		FuncName: "IsLeader",
   113  	}})
   114  
   115  }
   116  
   117  func (s *ContextFactorySuite) TestNewHookContextLeadershipContext(c *gc.C) {
   118  	s.testLeadershipContextWiring(c, func() *context.HookContext {
   119  		ctx, err := s.factory.HookContext(hook.Info{Kind: hooks.ConfigChanged})
   120  		c.Assert(err, jc.ErrorIsNil)
   121  		return ctx
   122  	})
   123  }
   124  
   125  func (s *ContextFactorySuite) TestNewCommandContextLeadershipContext(c *gc.C) {
   126  	s.testLeadershipContextWiring(c, func() *context.HookContext {
   127  		ctx, err := s.factory.CommandContext(context.CommandInfo{RelationId: -1})
   128  		c.Assert(err, jc.ErrorIsNil)
   129  		return ctx
   130  	})
   131  }
   132  
   133  func (s *ContextFactorySuite) TestNewActionContextLeadershipContext(c *gc.C) {
   134  	s.testLeadershipContextWiring(c, func() *context.HookContext {
   135  		s.SetCharm(c, "dummy")
   136  		action, err := s.State.EnqueueAction(s.unit.Tag(), "snapshot", nil)
   137  		c.Assert(err, jc.ErrorIsNil)
   138  
   139  		actionData := &context.ActionData{
   140  			Name:       action.Name(),
   141  			Tag:        names.NewActionTag(action.Id()),
   142  			Params:     action.Parameters(),
   143  			ResultsMap: map[string]interface{}{},
   144  		}
   145  
   146  		ctx, err := s.factory.ActionContext(actionData)
   147  		c.Assert(err, jc.ErrorIsNil)
   148  		return ctx
   149  	})
   150  }
   151  
   152  func (s *ContextFactorySuite) TestRelationHookContext(c *gc.C) {
   153  	hi := hook.Info{
   154  		Kind:       hooks.RelationBroken,
   155  		RelationId: 1,
   156  	}
   157  	ctx, err := s.factory.HookContext(hi)
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	s.AssertCoreContext(c, ctx)
   160  	s.AssertNotActionContext(c, ctx)
   161  	s.AssertRelationContext(c, ctx, 1, "")
   162  	s.AssertNotStorageContext(c, ctx)
   163  }
   164  
   165  func (s *ContextFactorySuite) TestNewHookContextWithStorage(c *gc.C) {
   166  	// We need to set up a unit that has storage metadata defined.
   167  	ch := s.AddTestingCharm(c, "storage-block")
   168  	sCons := map[string]state.StorageConstraints{
   169  		"data": {Pool: "", Size: 1024, Count: 1},
   170  	}
   171  	service := s.AddTestingServiceWithStorage(c, "storage-block", ch, sCons)
   172  	s.machine = nil // allocate a new machine
   173  	unit := s.AddUnit(c, service)
   174  
   175  	storageAttachments, err := s.State.UnitStorageAttachments(unit.UnitTag())
   176  	c.Assert(err, jc.ErrorIsNil)
   177  	c.Assert(storageAttachments, gc.HasLen, 1)
   178  	storageTag := storageAttachments[0].StorageInstance()
   179  
   180  	volume, err := s.State.StorageInstanceVolume(storageTag)
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	volumeTag := volume.VolumeTag()
   183  	machineTag := s.machine.MachineTag()
   184  
   185  	err = s.State.SetVolumeInfo(
   186  		volumeTag, state.VolumeInfo{
   187  			VolumeId: "vol-123",
   188  			Size:     456,
   189  		},
   190  	)
   191  	c.Assert(err, jc.ErrorIsNil)
   192  	err = s.State.SetVolumeAttachmentInfo(
   193  		machineTag, volumeTag, state.VolumeAttachmentInfo{
   194  			DeviceName: "sdb",
   195  		},
   196  	)
   197  	c.Assert(err, jc.ErrorIsNil)
   198  
   199  	password, err := utils.RandomPassword()
   200  	err = unit.SetPassword(password)
   201  	c.Assert(err, jc.ErrorIsNil)
   202  	st := s.OpenAPIAs(c, unit.Tag(), password)
   203  	uniter, err := st.Uniter()
   204  	c.Assert(err, jc.ErrorIsNil)
   205  
   206  	contextFactory, err := context.NewContextFactory(
   207  		uniter,
   208  		unit.Tag().(names.UnitTag),
   209  		runnertesting.FakeTracker{},
   210  		s.getRelationInfos,
   211  		s.storage,
   212  		s.paths,
   213  		coretesting.NewClock(time.Time{}),
   214  	)
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	ctx, err := contextFactory.HookContext(hook.Info{
   217  		Kind:      hooks.StorageAttached,
   218  		StorageId: "data/0",
   219  	})
   220  	c.Assert(err, jc.ErrorIsNil)
   221  	c.Assert(ctx.UnitName(), gc.Equals, "storage-block/0")
   222  	s.AssertStorageContext(c, ctx, "data/0", storage.StorageAttachmentInfo{
   223  		Kind:     storage.StorageKindBlock,
   224  		Location: "/dev/sdb",
   225  	})
   226  	s.AssertNotActionContext(c, ctx)
   227  	s.AssertNotRelationContext(c, ctx)
   228  }
   229  
   230  func (s *ContextFactorySuite) TestActionContext(c *gc.C) {
   231  	s.SetCharm(c, "dummy")
   232  	action, err := s.State.EnqueueAction(s.unit.Tag(), "snapshot", nil)
   233  	c.Assert(err, jc.ErrorIsNil)
   234  
   235  	actionData := &context.ActionData{
   236  		Name:       action.Name(),
   237  		Tag:        names.NewActionTag(action.Id()),
   238  		Params:     action.Parameters(),
   239  		ResultsMap: map[string]interface{}{},
   240  	}
   241  
   242  	ctx, err := s.factory.ActionContext(actionData)
   243  	c.Assert(err, jc.ErrorIsNil)
   244  
   245  	s.AssertCoreContext(c, ctx)
   246  	s.AssertActionContext(c, ctx)
   247  	s.AssertNotRelationContext(c, ctx)
   248  	s.AssertNotStorageContext(c, ctx)
   249  }
   250  
   251  func (s *ContextFactorySuite) TestCommandContext(c *gc.C) {
   252  	ctx, err := s.factory.CommandContext(context.CommandInfo{RelationId: -1})
   253  	c.Assert(err, jc.ErrorIsNil)
   254  
   255  	s.AssertCoreContext(c, ctx)
   256  	s.AssertNotActionContext(c, ctx)
   257  	s.AssertNotRelationContext(c, ctx)
   258  	s.AssertNotStorageContext(c, ctx)
   259  }
   260  
   261  func (s *ContextFactorySuite) TestCommandContextNoRelation(c *gc.C) {
   262  	ctx, err := s.factory.CommandContext(context.CommandInfo{RelationId: -1})
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	s.AssertCoreContext(c, ctx)
   265  	s.AssertNotActionContext(c, ctx)
   266  	s.AssertNotRelationContext(c, ctx)
   267  	s.AssertNotStorageContext(c, ctx)
   268  }
   269  
   270  func (s *ContextFactorySuite) TestNewCommandContextForceNoRemoteUnit(c *gc.C) {
   271  	ctx, err := s.factory.CommandContext(context.CommandInfo{
   272  		RelationId: 0, ForceRemoteUnit: true,
   273  	})
   274  	c.Assert(err, jc.ErrorIsNil)
   275  	s.AssertCoreContext(c, ctx)
   276  	s.AssertNotActionContext(c, ctx)
   277  	s.AssertRelationContext(c, ctx, 0, "")
   278  	s.AssertNotStorageContext(c, ctx)
   279  }
   280  
   281  func (s *ContextFactorySuite) TestNewCommandContextForceRemoteUnitMissing(c *gc.C) {
   282  	ctx, err := s.factory.CommandContext(context.CommandInfo{
   283  		RelationId: 0, RemoteUnitName: "blah/123", ForceRemoteUnit: true,
   284  	})
   285  	c.Assert(err, gc.IsNil)
   286  	s.AssertCoreContext(c, ctx)
   287  	s.AssertNotActionContext(c, ctx)
   288  	s.AssertRelationContext(c, ctx, 0, "blah/123")
   289  	s.AssertNotStorageContext(c, ctx)
   290  }
   291  
   292  func (s *ContextFactorySuite) TestNewCommandContextInferRemoteUnit(c *gc.C) {
   293  	s.membership[0] = []string{"foo/2"}
   294  	ctx, err := s.factory.CommandContext(context.CommandInfo{RelationId: 0})
   295  	c.Assert(err, jc.ErrorIsNil)
   296  	s.AssertCoreContext(c, ctx)
   297  	s.AssertNotActionContext(c, ctx)
   298  	s.AssertRelationContext(c, ctx, 0, "foo/2")
   299  	s.AssertNotStorageContext(c, ctx)
   300  }
   301  
   302  func (s *ContextFactorySuite) TestNewHookContextPrunesNonMemberCaches(c *gc.C) {
   303  
   304  	// Write cached member settings for a member and a non-member.
   305  	s.setUpCacheMethods(c)
   306  	s.membership[0] = []string{"rel0/0"}
   307  	s.updateCache(0, "rel0/0", params.Settings{"keep": "me"})
   308  	s.updateCache(0, "rel0/1", params.Settings{"drop": "me"})
   309  
   310  	ctx, err := s.factory.HookContext(hook.Info{Kind: hooks.Install})
   311  	c.Assert(err, jc.ErrorIsNil)
   312  
   313  	settings0, found := s.getCache(0, "rel0/0")
   314  	c.Assert(found, jc.IsTrue)
   315  	c.Assert(settings0, jc.DeepEquals, params.Settings{"keep": "me"})
   316  
   317  	settings1, found := s.getCache(0, "rel0/1")
   318  	c.Assert(found, jc.IsFalse)
   319  	c.Assert(settings1, gc.IsNil)
   320  
   321  	// Check the caches are being used by the context relations.
   322  	relCtx, err := ctx.Relation(0)
   323  	c.Assert(err, jc.ErrorIsNil)
   324  
   325  	// Verify that the settings really were cached by trying to look them up.
   326  	// Nothing's really in scope, so the call would fail if they weren't.
   327  	settings0, err = relCtx.ReadSettings("rel0/0")
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	c.Assert(settings0, jc.DeepEquals, params.Settings{"keep": "me"})
   330  
   331  	// Verify that the non-member settings were purged by looking them up and
   332  	// checking for the expected error.
   333  	settings1, err = relCtx.ReadSettings("rel0/1")
   334  	c.Assert(settings1, gc.IsNil)
   335  	c.Assert(err, gc.ErrorMatches, "permission denied")
   336  }
   337  
   338  func (s *ContextFactorySuite) TestNewHookContextRelationJoinedUpdatesRelationContextAndCaches(c *gc.C) {
   339  	// Write some cached settings for r/0, so we can verify the cache gets cleared.
   340  	s.setUpCacheMethods(c)
   341  	s.membership[1] = []string{"r/0"}
   342  	s.updateCache(1, "r/0", params.Settings{"foo": "bar"})
   343  
   344  	ctx, err := s.factory.HookContext(hook.Info{
   345  		Kind:       hooks.RelationJoined,
   346  		RelationId: 1,
   347  		RemoteUnit: "r/0",
   348  	})
   349  	c.Assert(err, jc.ErrorIsNil)
   350  	s.AssertCoreContext(c, ctx)
   351  	s.AssertNotActionContext(c, ctx)
   352  	s.AssertNotStorageContext(c, ctx)
   353  	rel := s.AssertRelationContext(c, ctx, 1, "r/0")
   354  	c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/0"})
   355  	cached0, member := s.getCache(1, "r/0")
   356  	c.Assert(cached0, gc.IsNil)
   357  	c.Assert(member, jc.IsTrue)
   358  }
   359  
   360  func (s *ContextFactorySuite) TestNewHookContextRelationChangedUpdatesRelationContextAndCaches(c *gc.C) {
   361  	// Update member settings to have actual values, so we can check that
   362  	// the change for r/4 clears its cache but leaves r/0's alone.
   363  	s.setUpCacheMethods(c)
   364  	s.membership[1] = []string{"r/0", "r/4"}
   365  	s.updateCache(1, "r/0", params.Settings{"foo": "bar"})
   366  	s.updateCache(1, "r/4", params.Settings{"baz": "qux"})
   367  
   368  	ctx, err := s.factory.HookContext(hook.Info{
   369  		Kind:       hooks.RelationChanged,
   370  		RelationId: 1,
   371  		RemoteUnit: "r/4",
   372  	})
   373  	c.Assert(err, jc.ErrorIsNil)
   374  	s.AssertCoreContext(c, ctx)
   375  	s.AssertNotActionContext(c, ctx)
   376  	s.AssertNotStorageContext(c, ctx)
   377  	rel := s.AssertRelationContext(c, ctx, 1, "r/4")
   378  	c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/0", "r/4"})
   379  	cached0, member := s.getCache(1, "r/0")
   380  	c.Assert(cached0, jc.DeepEquals, params.Settings{"foo": "bar"})
   381  	c.Assert(member, jc.IsTrue)
   382  	cached4, member := s.getCache(1, "r/4")
   383  	c.Assert(cached4, gc.IsNil)
   384  	c.Assert(member, jc.IsTrue)
   385  }
   386  
   387  func (s *ContextFactorySuite) TestNewHookContextRelationDepartedUpdatesRelationContextAndCaches(c *gc.C) {
   388  	// Update member settings to have actual values, so we can check that
   389  	// the depart for r/0 leaves r/4's cache alone (while discarding r/0's).
   390  	s.setUpCacheMethods(c)
   391  	s.membership[1] = []string{"r/0", "r/4"}
   392  	s.updateCache(1, "r/0", params.Settings{"foo": "bar"})
   393  	s.updateCache(1, "r/4", params.Settings{"baz": "qux"})
   394  
   395  	ctx, err := s.factory.HookContext(hook.Info{
   396  		Kind:       hooks.RelationDeparted,
   397  		RelationId: 1,
   398  		RemoteUnit: "r/0",
   399  	})
   400  	c.Assert(err, jc.ErrorIsNil)
   401  	s.AssertCoreContext(c, ctx)
   402  	s.AssertNotActionContext(c, ctx)
   403  	s.AssertNotStorageContext(c, ctx)
   404  	rel := s.AssertRelationContext(c, ctx, 1, "r/0")
   405  	c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/4"})
   406  	cached0, member := s.getCache(1, "r/0")
   407  	c.Assert(cached0, gc.IsNil)
   408  	c.Assert(member, jc.IsFalse)
   409  	cached4, member := s.getCache(1, "r/4")
   410  	c.Assert(cached4, jc.DeepEquals, params.Settings{"baz": "qux"})
   411  	c.Assert(member, jc.IsTrue)
   412  }
   413  
   414  func (s *ContextFactorySuite) TestNewHookContextRelationBrokenRetainsCaches(c *gc.C) {
   415  	// Note that this is bizarre and unrealistic, because we would never usually
   416  	// run relation-broken on a non-empty relation. But verfying that the settings
   417  	// stick around allows us to verify that there's no special handling for that
   418  	// hook -- as there should not be, because the relation caches will be discarded
   419  	// for the *next* hook, which will be constructed with the current set of known
   420  	// relations and ignore everything else.
   421  	s.setUpCacheMethods(c)
   422  	s.membership[1] = []string{"r/0", "r/4"}
   423  	s.updateCache(1, "r/0", params.Settings{"foo": "bar"})
   424  	s.updateCache(1, "r/4", params.Settings{"baz": "qux"})
   425  
   426  	ctx, err := s.factory.HookContext(hook.Info{
   427  		Kind:       hooks.RelationBroken,
   428  		RelationId: 1,
   429  	})
   430  	c.Assert(err, jc.ErrorIsNil)
   431  	rel := s.AssertRelationContext(c, ctx, 1, "")
   432  	c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/0", "r/4"})
   433  	cached0, member := s.getCache(1, "r/0")
   434  	c.Assert(cached0, jc.DeepEquals, params.Settings{"foo": "bar"})
   435  	c.Assert(member, jc.IsTrue)
   436  	cached4, member := s.getCache(1, "r/4")
   437  	c.Assert(cached4, jc.DeepEquals, params.Settings{"baz": "qux"})
   438  	c.Assert(member, jc.IsTrue)
   439  }
   440  
   441  type StubLeadershipContext struct {
   442  	context.LeadershipContext
   443  	*testing.Stub
   444  }
   445  
   446  func (stub *StubLeadershipContext) IsLeader() (bool, error) {
   447  	stub.MethodCall(stub, "IsLeader")
   448  	return false, stub.NextErr()
   449  }