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

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package runner_test
     5  
     6  import (
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    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.v5/hooks"
    18  
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/state"
    21  	"github.com/juju/juju/storage"
    22  	"github.com/juju/juju/testcharms"
    23  	"github.com/juju/juju/worker/uniter/hook"
    24  	"github.com/juju/juju/worker/uniter/runner"
    25  )
    26  
    27  type FactorySuite struct {
    28  	HookContextSuite
    29  	paths      RealPaths
    30  	factory    runner.Factory
    31  	membership map[int][]string
    32  }
    33  
    34  var _ = gc.Suite(&FactorySuite{})
    35  
    36  func (s *FactorySuite) SetUpTest(c *gc.C) {
    37  	s.HookContextSuite.SetUpTest(c)
    38  	s.paths = NewRealPaths(c)
    39  	s.membership = map[int][]string{}
    40  
    41  	contextFactory, err := runner.NewContextFactory(
    42  		s.uniter,
    43  		s.unit.Tag().(names.UnitTag),
    44  		fakeTracker{},
    45  		s.getRelationInfos,
    46  		s.storage,
    47  		s.paths,
    48  	)
    49  	c.Assert(err, jc.ErrorIsNil)
    50  
    51  	factory, err := runner.NewFactory(
    52  		s.uniter,
    53  		s.paths,
    54  		contextFactory,
    55  	)
    56  	c.Assert(err, jc.ErrorIsNil)
    57  	s.factory = factory
    58  }
    59  
    60  func (s *FactorySuite) SetCharm(c *gc.C, name string) {
    61  	err := os.RemoveAll(s.paths.charm)
    62  	c.Assert(err, jc.ErrorIsNil)
    63  	err = fs.Copy(testcharms.Repo.CharmDirPath(name), s.paths.charm)
    64  	c.Assert(err, jc.ErrorIsNil)
    65  }
    66  
    67  func (s *FactorySuite) getRelationInfos() map[int]*runner.RelationInfo {
    68  	info := map[int]*runner.RelationInfo{}
    69  	for relId, relUnit := range s.apiRelunits {
    70  		info[relId] = &runner.RelationInfo{
    71  			RelationUnit: relUnit,
    72  			MemberNames:  s.membership[relId],
    73  		}
    74  	}
    75  	return info
    76  }
    77  
    78  func (s *FactorySuite) setUpCacheMethods(c *gc.C) {
    79  	// The factory's caches are created lazily, so it doesn't have any at all to
    80  	// begin with. Creating and discarding a context lets us call updateCache
    81  	// without panicking. (IMO this is less invasive that making updateCache
    82  	// responsible for creating missing caches etc.)
    83  	_, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.Install})
    84  	c.Assert(err, jc.ErrorIsNil)
    85  }
    86  
    87  func (s *FactorySuite) updateCache(relId int, unitName string, settings params.Settings) {
    88  	runner.UpdateCachedSettings(s.factory, relId, unitName, settings)
    89  }
    90  
    91  func (s *FactorySuite) getCache(relId int, unitName string) (params.Settings, bool) {
    92  	return runner.CachedSettings(s.factory, relId, unitName)
    93  }
    94  
    95  func (s *FactorySuite) AssertPaths(c *gc.C, rnr runner.Runner) {
    96  	c.Assert(runner.RunnerPaths(rnr), gc.DeepEquals, s.paths)
    97  }
    98  
    99  func (s *FactorySuite) TestNewCommandRunnerNoRelation(c *gc.C) {
   100  	rnr, err := s.factory.NewCommandRunner(runner.CommandInfo{RelationId: -1})
   101  	c.Assert(err, jc.ErrorIsNil)
   102  	s.AssertPaths(c, rnr)
   103  	ctx := rnr.Context()
   104  	s.AssertCoreContext(c, ctx)
   105  	s.AssertNotActionContext(c, ctx)
   106  	s.AssertNotRelationContext(c, ctx)
   107  	s.AssertNotStorageContext(c, ctx)
   108  }
   109  
   110  func (s *FactorySuite) TestNewCommandRunnerRelationIdDoesNotExist(c *gc.C) {
   111  	for _, value := range []bool{true, false} {
   112  		_, err := s.factory.NewCommandRunner(runner.CommandInfo{
   113  			RelationId: 12, ForceRemoteUnit: value,
   114  		})
   115  		c.Check(err, gc.ErrorMatches, `unknown relation id: 12`)
   116  	}
   117  }
   118  
   119  func (s *FactorySuite) TestNewCommandRunnerRemoteUnitInvalid(c *gc.C) {
   120  	for _, value := range []bool{true, false} {
   121  		_, err := s.factory.NewCommandRunner(runner.CommandInfo{
   122  			RelationId: 0, RemoteUnitName: "blah", ForceRemoteUnit: value,
   123  		})
   124  		c.Check(err, gc.ErrorMatches, `invalid remote unit: blah`)
   125  	}
   126  }
   127  
   128  func (s *FactorySuite) TestNewCommandRunnerRemoteUnitInappropriate(c *gc.C) {
   129  	for _, value := range []bool{true, false} {
   130  		_, err := s.factory.NewCommandRunner(runner.CommandInfo{
   131  			RelationId: -1, RemoteUnitName: "blah/123", ForceRemoteUnit: value,
   132  		})
   133  		c.Check(err, gc.ErrorMatches, `remote unit provided without a relation: blah/123`)
   134  	}
   135  }
   136  
   137  func (s *FactorySuite) TestNewCommandRunnerEmptyRelation(c *gc.C) {
   138  	_, err := s.factory.NewCommandRunner(runner.CommandInfo{RelationId: 1})
   139  	c.Check(err, gc.ErrorMatches, `cannot infer remote unit in empty relation 1`)
   140  }
   141  
   142  func (s *FactorySuite) TestNewCommandRunnerRemoteUnitAmbiguous(c *gc.C) {
   143  	s.membership[1] = []string{"foo/0", "foo/1"}
   144  	_, err := s.factory.NewCommandRunner(runner.CommandInfo{RelationId: 1})
   145  	c.Check(err, gc.ErrorMatches, `ambiguous remote unit; possibilities are \[foo/0 foo/1\]`)
   146  }
   147  
   148  func (s *FactorySuite) TestNewCommandRunnerRemoteUnitMissing(c *gc.C) {
   149  	s.membership[0] = []string{"foo/0", "foo/1"}
   150  	_, err := s.factory.NewCommandRunner(runner.CommandInfo{
   151  		RelationId: 0, RemoteUnitName: "blah/123",
   152  	})
   153  	c.Check(err, gc.ErrorMatches, `unknown remote unit blah/123; possibilities are \[foo/0 foo/1\]`)
   154  }
   155  
   156  func (s *FactorySuite) TestNewCommandRunnerForceNoRemoteUnit(c *gc.C) {
   157  	rnr, err := s.factory.NewCommandRunner(runner.CommandInfo{
   158  		RelationId: 0, ForceRemoteUnit: true,
   159  	})
   160  	c.Assert(err, jc.ErrorIsNil)
   161  	s.AssertPaths(c, rnr)
   162  	ctx := rnr.Context()
   163  	s.AssertCoreContext(c, ctx)
   164  	s.AssertNotActionContext(c, ctx)
   165  	s.AssertRelationContext(c, ctx, 0, "")
   166  	s.AssertNotStorageContext(c, ctx)
   167  }
   168  
   169  func (s *FactorySuite) TestNewCommandRunnerForceRemoteUnitMissing(c *gc.C) {
   170  	rnr, err := s.factory.NewCommandRunner(runner.CommandInfo{
   171  		RelationId: 0, RemoteUnitName: "blah/123", ForceRemoteUnit: true,
   172  	})
   173  	c.Assert(err, gc.IsNil)
   174  	ctx := rnr.Context()
   175  	s.AssertCoreContext(c, ctx)
   176  	s.AssertNotActionContext(c, ctx)
   177  	s.AssertRelationContext(c, ctx, 0, "blah/123")
   178  	s.AssertNotStorageContext(c, ctx)
   179  }
   180  
   181  func (s *FactorySuite) TestNewCommandRunnerInferRemoteUnit(c *gc.C) {
   182  	s.membership[0] = []string{"foo/2"}
   183  	rnr, err := s.factory.NewCommandRunner(runner.CommandInfo{RelationId: 0})
   184  	c.Assert(err, jc.ErrorIsNil)
   185  	s.AssertPaths(c, rnr)
   186  	ctx := rnr.Context()
   187  	s.AssertCoreContext(c, ctx)
   188  	s.AssertNotActionContext(c, ctx)
   189  	s.AssertRelationContext(c, ctx, 0, "foo/2")
   190  	s.AssertNotStorageContext(c, ctx)
   191  }
   192  
   193  func (s *FactorySuite) TestNewHookRunner(c *gc.C) {
   194  	rnr, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.ConfigChanged})
   195  	c.Assert(err, jc.ErrorIsNil)
   196  	s.AssertPaths(c, rnr)
   197  	ctx := rnr.Context()
   198  	s.AssertCoreContext(c, ctx)
   199  	s.AssertNotActionContext(c, ctx)
   200  	s.AssertNotRelationContext(c, ctx)
   201  	s.AssertNotStorageContext(c, ctx)
   202  }
   203  
   204  func (s *FactorySuite) TestNewHookRunnerWithBadHook(c *gc.C) {
   205  	rnr, err := s.factory.NewHookRunner(hook.Info{})
   206  	c.Assert(rnr, gc.IsNil)
   207  	c.Assert(err, gc.ErrorMatches, `unknown hook kind ""`)
   208  }
   209  
   210  func (s *FactorySuite) TestNewHookRunnerWithStorage(c *gc.C) {
   211  	// We need to set up a unit that has storage metadata defined.
   212  	ch := s.AddTestingCharm(c, "storage-block")
   213  	sCons := map[string]state.StorageConstraints{
   214  		"data": {Pool: "", Size: 1024, Count: 1},
   215  	}
   216  	service := s.AddTestingServiceWithStorage(c, "storage-block", ch, sCons)
   217  	s.machine = nil // allocate a new machine
   218  	unit := s.AddUnit(c, service)
   219  
   220  	storageAttachments, err := s.State.UnitStorageAttachments(unit.UnitTag())
   221  	c.Assert(err, jc.ErrorIsNil)
   222  	c.Assert(storageAttachments, gc.HasLen, 1)
   223  	storageTag := storageAttachments[0].StorageInstance()
   224  
   225  	volume, err := s.State.StorageInstanceVolume(storageTag)
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	volumeTag := volume.VolumeTag()
   228  	machineTag := s.machine.MachineTag()
   229  
   230  	err = s.State.SetVolumeInfo(
   231  		volumeTag, state.VolumeInfo{
   232  			VolumeId: "vol-123",
   233  			Size:     456,
   234  		},
   235  	)
   236  	c.Assert(err, jc.ErrorIsNil)
   237  	err = s.State.SetVolumeAttachmentInfo(
   238  		machineTag, volumeTag, state.VolumeAttachmentInfo{
   239  			DeviceName: "sdb",
   240  		},
   241  	)
   242  	c.Assert(err, jc.ErrorIsNil)
   243  
   244  	password, err := utils.RandomPassword()
   245  	err = unit.SetPassword(password)
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	st := s.OpenAPIAs(c, unit.Tag(), password)
   248  	uniter, err := st.Uniter()
   249  	c.Assert(err, jc.ErrorIsNil)
   250  
   251  	contextFactory, err := runner.NewContextFactory(
   252  		uniter,
   253  		unit.Tag().(names.UnitTag),
   254  		fakeTracker{},
   255  		s.getRelationInfos,
   256  		s.storage,
   257  		s.paths,
   258  	)
   259  	c.Assert(err, jc.ErrorIsNil)
   260  	factory, err := runner.NewFactory(
   261  		uniter,
   262  		s.paths,
   263  		contextFactory,
   264  	)
   265  	c.Assert(err, jc.ErrorIsNil)
   266  
   267  	rnr, err := factory.NewHookRunner(hook.Info{
   268  		Kind:      hooks.StorageAttached,
   269  		StorageId: "data/0",
   270  	})
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	s.AssertPaths(c, rnr)
   273  	ctx := rnr.Context()
   274  	c.Assert(ctx.UnitName(), gc.Equals, "storage-block/0")
   275  	s.AssertStorageContext(c, ctx, "data/0", storage.StorageAttachmentInfo{
   276  		Kind:     storage.StorageKindBlock,
   277  		Location: "/dev/sdb",
   278  	})
   279  	s.AssertNotActionContext(c, ctx)
   280  	s.AssertNotRelationContext(c, ctx)
   281  }
   282  
   283  func (s *FactorySuite) TestNewHookRunnerWithRelation(c *gc.C) {
   284  	rnr, err := s.factory.NewHookRunner(hook.Info{
   285  		Kind:       hooks.RelationBroken,
   286  		RelationId: 1,
   287  	})
   288  	c.Assert(err, jc.ErrorIsNil)
   289  	s.AssertPaths(c, rnr)
   290  	ctx := rnr.Context()
   291  	s.AssertCoreContext(c, ctx)
   292  	s.AssertNotActionContext(c, ctx)
   293  	s.AssertRelationContext(c, ctx, 1, "")
   294  	s.AssertNotStorageContext(c, ctx)
   295  }
   296  
   297  func (s *FactorySuite) TestNewHookRunnerPrunesNonMemberCaches(c *gc.C) {
   298  
   299  	// Write cached member settings for a member and a non-member.
   300  	s.setUpCacheMethods(c)
   301  	s.membership[0] = []string{"rel0/0"}
   302  	s.updateCache(0, "rel0/0", params.Settings{"keep": "me"})
   303  	s.updateCache(0, "rel0/1", params.Settings{"drop": "me"})
   304  
   305  	rnr, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.Install})
   306  	c.Assert(err, jc.ErrorIsNil)
   307  	s.AssertPaths(c, rnr)
   308  	ctx := rnr.Context()
   309  
   310  	settings0, found := s.getCache(0, "rel0/0")
   311  	c.Assert(found, jc.IsTrue)
   312  	c.Assert(settings0, jc.DeepEquals, params.Settings{"keep": "me"})
   313  
   314  	settings1, found := s.getCache(0, "rel0/1")
   315  	c.Assert(found, jc.IsFalse)
   316  	c.Assert(settings1, gc.IsNil)
   317  
   318  	// Check the caches are being used by the context relations.
   319  	relCtx, found := ctx.Relation(0)
   320  	c.Assert(found, jc.IsTrue)
   321  
   322  	// Verify that the settings really were cached by trying to look them up.
   323  	// Nothing's really in scope, so the call would fail if they weren't.
   324  	settings0, err = relCtx.ReadSettings("rel0/0")
   325  	c.Assert(err, jc.ErrorIsNil)
   326  	c.Assert(settings0, jc.DeepEquals, params.Settings{"keep": "me"})
   327  
   328  	// Verify that the non-member settings were purged by looking them up and
   329  	// checking for the expected error.
   330  	settings1, err = relCtx.ReadSettings("rel0/1")
   331  	c.Assert(settings1, gc.IsNil)
   332  	c.Assert(err, gc.ErrorMatches, "permission denied")
   333  }
   334  
   335  func (s *FactorySuite) TestNewHookRunnerRelationJoinedUpdatesRelationContextAndCaches(c *gc.C) {
   336  	// Write some cached settings for r/0, so we can verify the cache gets cleared.
   337  	s.setUpCacheMethods(c)
   338  	s.membership[1] = []string{"r/0"}
   339  	s.updateCache(1, "r/0", params.Settings{"foo": "bar"})
   340  
   341  	rnr, err := s.factory.NewHookRunner(hook.Info{
   342  		Kind:       hooks.RelationJoined,
   343  		RelationId: 1,
   344  		RemoteUnit: "r/0",
   345  	})
   346  	c.Assert(err, jc.ErrorIsNil)
   347  	s.AssertPaths(c, rnr)
   348  	ctx := rnr.Context()
   349  	s.AssertCoreContext(c, ctx)
   350  	s.AssertNotActionContext(c, ctx)
   351  	s.AssertNotStorageContext(c, ctx)
   352  	rel := s.AssertRelationContext(c, ctx, 1, "r/0")
   353  	c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/0"})
   354  	cached0, member := s.getCache(1, "r/0")
   355  	c.Assert(cached0, gc.IsNil)
   356  	c.Assert(member, jc.IsTrue)
   357  }
   358  
   359  func (s *FactorySuite) TestNewHookRunnerRelationChangedUpdatesRelationContextAndCaches(c *gc.C) {
   360  	// Update member settings to have actual values, so we can check that
   361  	// the change for r/4 clears its cache but leaves r/0's alone.
   362  	s.setUpCacheMethods(c)
   363  	s.membership[1] = []string{"r/0", "r/4"}
   364  	s.updateCache(1, "r/0", params.Settings{"foo": "bar"})
   365  	s.updateCache(1, "r/4", params.Settings{"baz": "qux"})
   366  
   367  	rnr, err := s.factory.NewHookRunner(hook.Info{
   368  		Kind:       hooks.RelationChanged,
   369  		RelationId: 1,
   370  		RemoteUnit: "r/4",
   371  	})
   372  	c.Assert(err, jc.ErrorIsNil)
   373  	s.AssertPaths(c, rnr)
   374  	ctx := rnr.Context()
   375  	s.AssertCoreContext(c, ctx)
   376  	s.AssertNotActionContext(c, ctx)
   377  	s.AssertNotStorageContext(c, ctx)
   378  	rel := s.AssertRelationContext(c, ctx, 1, "r/4")
   379  	c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/0", "r/4"})
   380  	cached0, member := s.getCache(1, "r/0")
   381  	c.Assert(cached0, jc.DeepEquals, params.Settings{"foo": "bar"})
   382  	c.Assert(member, jc.IsTrue)
   383  	cached4, member := s.getCache(1, "r/4")
   384  	c.Assert(cached4, gc.IsNil)
   385  	c.Assert(member, jc.IsTrue)
   386  }
   387  
   388  func (s *FactorySuite) TestNewHookRunnerRelationDepartedUpdatesRelationContextAndCaches(c *gc.C) {
   389  	// Update member settings to have actual values, so we can check that
   390  	// the depart for r/0 leaves r/4's cache alone (while discarding r/0's).
   391  	s.setUpCacheMethods(c)
   392  	s.membership[1] = []string{"r/0", "r/4"}
   393  	s.updateCache(1, "r/0", params.Settings{"foo": "bar"})
   394  	s.updateCache(1, "r/4", params.Settings{"baz": "qux"})
   395  
   396  	rnr, err := s.factory.NewHookRunner(hook.Info{
   397  		Kind:       hooks.RelationDeparted,
   398  		RelationId: 1,
   399  		RemoteUnit: "r/0",
   400  	})
   401  	c.Assert(err, jc.ErrorIsNil)
   402  	s.AssertPaths(c, rnr)
   403  	ctx := rnr.Context()
   404  	s.AssertCoreContext(c, ctx)
   405  	s.AssertNotActionContext(c, ctx)
   406  	s.AssertNotStorageContext(c, ctx)
   407  	rel := s.AssertRelationContext(c, ctx, 1, "r/0")
   408  	c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/4"})
   409  	cached0, member := s.getCache(1, "r/0")
   410  	c.Assert(cached0, gc.IsNil)
   411  	c.Assert(member, jc.IsFalse)
   412  	cached4, member := s.getCache(1, "r/4")
   413  	c.Assert(cached4, jc.DeepEquals, params.Settings{"baz": "qux"})
   414  	c.Assert(member, jc.IsTrue)
   415  }
   416  
   417  func (s *FactorySuite) TestNewHookRunnerRelationBrokenRetainsCaches(c *gc.C) {
   418  	// Note that this is bizarre and unrealistic, because we would never usually
   419  	// run relation-broken on a non-empty relation. But verfying that the settings
   420  	// stick around allows us to verify that there's no special handling for that
   421  	// hook -- as there should not be, because the relation caches will be discarded
   422  	// for the *next* hook, which will be constructed with the current set of known
   423  	// relations and ignore everything else.
   424  	s.setUpCacheMethods(c)
   425  	s.membership[1] = []string{"r/0", "r/4"}
   426  	s.updateCache(1, "r/0", params.Settings{"foo": "bar"})
   427  	s.updateCache(1, "r/4", params.Settings{"baz": "qux"})
   428  
   429  	rnr, err := s.factory.NewHookRunner(hook.Info{
   430  		Kind:       hooks.RelationBroken,
   431  		RelationId: 1,
   432  	})
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	s.AssertPaths(c, rnr)
   435  	ctx := rnr.Context()
   436  	rel := s.AssertRelationContext(c, ctx, 1, "")
   437  	c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/0", "r/4"})
   438  	cached0, member := s.getCache(1, "r/0")
   439  	c.Assert(cached0, jc.DeepEquals, params.Settings{"foo": "bar"})
   440  	c.Assert(member, jc.IsTrue)
   441  	cached4, member := s.getCache(1, "r/4")
   442  	c.Assert(cached4, jc.DeepEquals, params.Settings{"baz": "qux"})
   443  	c.Assert(member, jc.IsTrue)
   444  }
   445  
   446  func (s *FactorySuite) TestNewHookRunnerWithBadRelation(c *gc.C) {
   447  	rnr, err := s.factory.NewHookRunner(hook.Info{
   448  		Kind:       hooks.RelationBroken,
   449  		RelationId: 12345,
   450  	})
   451  	c.Assert(rnr, gc.IsNil)
   452  	c.Assert(err, gc.ErrorMatches, `unknown relation id: 12345`)
   453  }
   454  
   455  func (s *FactorySuite) TestNewHookRunnerMetricsDisabledHook(c *gc.C) {
   456  	s.SetCharm(c, "metered")
   457  	rnr, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.Install})
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	s.AssertPaths(c, rnr)
   460  	ctx := rnr.Context()
   461  	err = ctx.AddMetric("key", "value", time.Now())
   462  	c.Assert(err, gc.ErrorMatches, "metrics disabled")
   463  }
   464  
   465  func (s *FactorySuite) TestNewHookRunnerMetricsDisabledUndeclared(c *gc.C) {
   466  	s.SetCharm(c, "mysql")
   467  	rnr, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.CollectMetrics})
   468  	c.Assert(err, jc.ErrorIsNil)
   469  	s.AssertPaths(c, rnr)
   470  	ctx := rnr.Context()
   471  	err = ctx.AddMetric("key", "value", time.Now())
   472  	c.Assert(err, gc.ErrorMatches, "metrics disabled")
   473  }
   474  
   475  func (s *FactorySuite) TestNewHookRunnerMetricsDeclarationError(c *gc.C) {
   476  	rnr, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.CollectMetrics})
   477  	c.Assert(errors.Cause(err), jc.Satisfies, os.IsNotExist)
   478  	c.Assert(rnr, gc.IsNil)
   479  }
   480  
   481  func (s *FactorySuite) TestNewHookRunnerMetricsEnabled(c *gc.C) {
   482  	s.SetCharm(c, "metered")
   483  
   484  	rnr, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.CollectMetrics})
   485  	c.Assert(err, jc.ErrorIsNil)
   486  	s.AssertPaths(c, rnr)
   487  	ctx := rnr.Context()
   488  	err = ctx.AddMetric("pings", "0.5", time.Now())
   489  	c.Assert(err, jc.ErrorIsNil)
   490  }
   491  
   492  func (s *FactorySuite) TestNewActionRunnerGood(c *gc.C) {
   493  	s.SetCharm(c, "dummy")
   494  	action, err := s.State.EnqueueAction(s.unit.Tag(), "snapshot", map[string]interface{}{
   495  		"outfile": "/some/file.bz2",
   496  	})
   497  	c.Assert(err, jc.ErrorIsNil)
   498  	rnr, err := s.factory.NewActionRunner(action.Id())
   499  	c.Assert(err, jc.ErrorIsNil)
   500  	s.AssertPaths(c, rnr)
   501  	ctx := rnr.Context()
   502  	data, err := ctx.ActionData()
   503  	c.Assert(err, jc.ErrorIsNil)
   504  	c.Assert(data, jc.DeepEquals, &runner.ActionData{
   505  		Name: "snapshot",
   506  		Tag:  action.ActionTag(),
   507  		Params: map[string]interface{}{
   508  			"outfile": "/some/file.bz2",
   509  		},
   510  		ResultsMap: map[string]interface{}{},
   511  	})
   512  	vars := ctx.HookVars(s.paths)
   513  	c.Assert(len(vars) > 0, jc.IsTrue, gc.Commentf("expected HookVars but found none"))
   514  	combined := strings.Join(vars, "|")
   515  	c.Assert(combined, gc.Matches, `(^|.*\|)JUJU_ACTION_NAME=snapshot(\|.*|$)`)
   516  	c.Assert(combined, gc.Matches, `(^|.*\|)JUJU_ACTION_UUID=`+action.Id()+`(\|.*|$)`)
   517  	c.Assert(combined, gc.Matches, `(^|.*\|)JUJU_ACTION_TAG=`+action.Tag().String()+`(\|.*|$)`)
   518  }
   519  
   520  func (s *FactorySuite) TestNewActionRunnerBadCharm(c *gc.C) {
   521  	rnr, err := s.factory.NewActionRunner("irrelevant")
   522  	c.Assert(rnr, gc.IsNil)
   523  	c.Assert(errors.Cause(err), jc.Satisfies, os.IsNotExist)
   524  	c.Assert(err, gc.Not(jc.Satisfies), runner.IsBadActionError)
   525  }
   526  
   527  func (s *FactorySuite) TestNewActionRunnerBadName(c *gc.C) {
   528  	s.SetCharm(c, "dummy")
   529  	action, err := s.State.EnqueueAction(s.unit.Tag(), "no-such-action", nil)
   530  	c.Assert(err, jc.ErrorIsNil) // this will fail when using AddAction on unit
   531  	rnr, err := s.factory.NewActionRunner(action.Id())
   532  	c.Check(rnr, gc.IsNil)
   533  	c.Check(err, gc.ErrorMatches, "cannot run \"no-such-action\" action: not defined")
   534  	c.Check(err, jc.Satisfies, runner.IsBadActionError)
   535  }
   536  
   537  func (s *FactorySuite) TestNewActionRunnerBadParams(c *gc.C) {
   538  	s.SetCharm(c, "dummy")
   539  	action, err := s.State.EnqueueAction(s.unit.Tag(), "snapshot", map[string]interface{}{
   540  		"outfile": 123,
   541  	})
   542  	c.Assert(err, jc.ErrorIsNil) // this will fail when state is done right
   543  	rnr, err := s.factory.NewActionRunner(action.Id())
   544  	c.Check(rnr, gc.IsNil)
   545  	c.Check(err, gc.ErrorMatches, "cannot run \"snapshot\" action: .*")
   546  	c.Check(err, jc.Satisfies, runner.IsBadActionError)
   547  }
   548  
   549  func (s *FactorySuite) TestNewActionRunnerMissingAction(c *gc.C) {
   550  	s.SetCharm(c, "dummy")
   551  	action, err := s.State.EnqueueAction(s.unit.Tag(), "snapshot", nil)
   552  	c.Assert(err, jc.ErrorIsNil)
   553  	_, err = s.unit.CancelAction(action)
   554  	c.Assert(err, jc.ErrorIsNil)
   555  	rnr, err := s.factory.NewActionRunner(action.Id())
   556  	c.Check(rnr, gc.IsNil)
   557  	c.Check(err, gc.ErrorMatches, "action no longer available")
   558  	c.Check(err, gc.Equals, runner.ErrActionNotAvailable)
   559  }
   560  
   561  func (s *FactorySuite) TestNewActionRunnerUnauthAction(c *gc.C) {
   562  	s.SetCharm(c, "dummy")
   563  	otherUnit, err := s.service.AddUnit()
   564  	c.Assert(err, jc.ErrorIsNil)
   565  	action, err := s.State.EnqueueAction(otherUnit.Tag(), "snapshot", nil)
   566  	c.Assert(err, jc.ErrorIsNil)
   567  	rnr, err := s.factory.NewActionRunner(action.Id())
   568  	c.Check(rnr, gc.IsNil)
   569  	c.Check(err, gc.ErrorMatches, "action no longer available")
   570  	c.Check(err, gc.Equals, runner.ErrActionNotAvailable)
   571  }