1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
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"
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"
27
type FactorySuite struct {
30
factory runner.Factory
31
membership map[int][]string
34
var _ = gc.Suite(&FactorySuite{})
36
func (s *FactorySuite) SetUpTest(c *gc.C) {
37
s.HookContextSuite.SetUpTest(c)
38
s.paths = NewRealPaths(c)
39
s.membership = map[int][]string{}
41
contextFactory, err := runner.NewContextFactory(
43
s.unit.Tag().(names.UnitTag),
49
c.Assert(err, jc.ErrorIsNil)
51
factory, err := runner.NewFactory(
56
c.Assert(err, jc.ErrorIsNil)
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)
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],
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)
87
func (s *FactorySuite) updateCache(relId int, unitName string, settings params.Settings) {
88
runner.UpdateCachedSettings(s.factory, relId, unitName, settings)
91
func (s *FactorySuite) getCache(relId int, unitName string) (params.Settings, bool) {
92
return runner.CachedSettings(s.factory, relId, unitName)
95
func (s *FactorySuite) AssertPaths(c *gc.C, rnr runner.Runner) {
96
c.Assert(runner.RunnerPaths(rnr), gc.DeepEquals, s.paths)
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)
104
s.AssertCoreContext(c, ctx)
105
s.AssertNotActionContext(c, ctx)
106
s.AssertNotRelationContext(c, ctx)
107
s.AssertNotStorageContext(c, ctx)
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,
115
c.Check(err, gc.ErrorMatches, `unknown relation id: 12`)
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,
124
c.Check(err, gc.ErrorMatches, `invalid remote unit: blah`)
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,
133
c.Check(err, gc.ErrorMatches, `remote unit provided without a relation: blah/123`)
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`)
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\]`)
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",
153
c.Check(err, gc.ErrorMatches, `unknown remote unit blah/123; possibilities are \[foo/0 foo/1\]`)
156
func (s *FactorySuite) TestNewCommandRunnerForceNoRemoteUnit(c *gc.C) {
157
rnr, err := s.factory.NewCommandRunner(runner.CommandInfo{
158
RelationId: 0, ForceRemoteUnit: true,
160
c.Assert(err, jc.ErrorIsNil)
161
s.AssertPaths(c, rnr)
163
s.AssertCoreContext(c, ctx)
164
s.AssertNotActionContext(c, ctx)
165
s.AssertRelationContext(c, ctx, 0, "")
166
s.AssertNotStorageContext(c, ctx)
169
func (s *FactorySuite) TestNewCommandRunnerForceRemoteUnitMissing(c *gc.C) {
170
rnr, err := s.factory.NewCommandRunner(runner.CommandInfo{
171
RelationId: 0, RemoteUnitName: "blah/123", ForceRemoteUnit: true,
173
c.Assert(err, gc.IsNil)
175
s.AssertCoreContext(c, ctx)
176
s.AssertNotActionContext(c, ctx)
177
s.AssertRelationContext(c, ctx, 0, "blah/123")
178
s.AssertNotStorageContext(c, ctx)
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)
187
s.AssertCoreContext(c, ctx)
188
s.AssertNotActionContext(c, ctx)
189
s.AssertRelationContext(c, ctx, 0, "foo/2")
190
s.AssertNotStorageContext(c, ctx)
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)
198
s.AssertCoreContext(c, ctx)
199
s.AssertNotActionContext(c, ctx)
200
s.AssertNotRelationContext(c, ctx)
201
s.AssertNotStorageContext(c, ctx)
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 ""`)
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},
216
service := s.AddTestingServiceWithStorage(c, "storage-block", ch, sCons)
217
s.machine = nil // allocate a new machine
218
unit := s.AddUnit(c, service)
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()
225
volume, err := s.State.StorageInstanceVolume(storageTag)
226
c.Assert(err, jc.ErrorIsNil)
227
volumeTag := volume.VolumeTag()
228
machineTag := s.machine.MachineTag()
230
err = s.State.SetVolumeInfo(
231
volumeTag, state.VolumeInfo{
236
c.Assert(err, jc.ErrorIsNil)
237
err = s.State.SetVolumeAttachmentInfo(
238
machineTag, volumeTag, state.VolumeAttachmentInfo{
242
c.Assert(err, jc.ErrorIsNil)
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)
251
contextFactory, err := runner.NewContextFactory(
253
unit.Tag().(names.UnitTag),
259
c.Assert(err, jc.ErrorIsNil)
260
factory, err := runner.NewFactory(
265
c.Assert(err, jc.ErrorIsNil)
267
rnr, err := factory.NewHookRunner(hook.Info{
268
Kind: hooks.StorageAttached,
271
c.Assert(err, jc.ErrorIsNil)
272
s.AssertPaths(c, rnr)
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",
279
s.AssertNotActionContext(c, ctx)
280
s.AssertNotRelationContext(c, ctx)
283
func (s *FactorySuite) TestNewHookRunnerWithRelation(c *gc.C) {
284
rnr, err := s.factory.NewHookRunner(hook.Info{
285
Kind: hooks.RelationBroken,
288
c.Assert(err, jc.ErrorIsNil)
289
s.AssertPaths(c, rnr)
291
s.AssertCoreContext(c, ctx)
292
s.AssertNotActionContext(c, ctx)
293
s.AssertRelationContext(c, ctx, 1, "")
294
s.AssertNotStorageContext(c, ctx)
297
func (s *FactorySuite) TestNewHookRunnerPrunesNonMemberCaches(c *gc.C) {
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"})
305
rnr, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.Install})
306
c.Assert(err, jc.ErrorIsNil)
307
s.AssertPaths(c, rnr)
310
settings0, found := s.getCache(0, "rel0/0")
311
c.Assert(found, jc.IsTrue)
312
c.Assert(settings0, jc.DeepEquals, params.Settings{"keep": "me"})
314
settings1, found := s.getCache(0, "rel0/1")
315
c.Assert(found, jc.IsFalse)
316
c.Assert(settings1, gc.IsNil)
318
// Check the caches are being used by the context relations.
319
relCtx, found := ctx.Relation(0)
320
c.Assert(found, jc.IsTrue)
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"})
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")
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"})
341
rnr, err := s.factory.NewHookRunner(hook.Info{
342
Kind: hooks.RelationJoined,
346
c.Assert(err, jc.ErrorIsNil)
347
s.AssertPaths(c, rnr)
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)
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"})
367
rnr, err := s.factory.NewHookRunner(hook.Info{
368
Kind: hooks.RelationChanged,
372
c.Assert(err, jc.ErrorIsNil)
373
s.AssertPaths(c, rnr)
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)
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"})
396
rnr, err := s.factory.NewHookRunner(hook.Info{
397
Kind: hooks.RelationDeparted,
401
c.Assert(err, jc.ErrorIsNil)
402
s.AssertPaths(c, rnr)
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)
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"})
429
rnr, err := s.factory.NewHookRunner(hook.Info{
430
Kind: hooks.RelationBroken,
433
c.Assert(err, jc.ErrorIsNil)
434
s.AssertPaths(c, rnr)
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)
446
func (s *FactorySuite) TestNewHookRunnerWithBadRelation(c *gc.C) {
447
rnr, err := s.factory.NewHookRunner(hook.Info{
448
Kind: hooks.RelationBroken,
451
c.Assert(rnr, gc.IsNil)
452
c.Assert(err, gc.ErrorMatches, `unknown relation id: 12345`)
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)
461
err = ctx.AddMetric("key", "value", time.Now())
462
c.Assert(err, gc.ErrorMatches, "metrics disabled")
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)
471
err = ctx.AddMetric("key", "value", time.Now())
472
c.Assert(err, gc.ErrorMatches, "metrics disabled")
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)
481
func (s *FactorySuite) TestNewHookRunnerMetricsEnabled(c *gc.C) {
482
s.SetCharm(c, "metered")
484
rnr, err := s.factory.NewHookRunner(hook.Info{Kind: hooks.CollectMetrics})
485
c.Assert(err, jc.ErrorIsNil)
486
s.AssertPaths(c, rnr)
488
err = ctx.AddMetric("pings", "0.5", time.Now())
489
c.Assert(err, jc.ErrorIsNil)
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",
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)
502
data, err := ctx.ActionData()
503
c.Assert(err, jc.ErrorIsNil)
504
c.Assert(data, jc.DeepEquals, &runner.ActionData{
506
Tag: action.ActionTag(),
507
Params: map[string]interface{}{
508
"outfile": "/some/file.bz2",
510
ResultsMap: map[string]interface{}{},
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()+`(\|.*|$)`)
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)
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)
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{}{
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)
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)
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)