1
// Copyright 2014-2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"github.com/juju/errors"
12
jc "github.com/juju/testing/checkers"
14
"github.com/juju/utils"
15
gc "gopkg.in/check.v1"
16
"gopkg.in/juju/names.v2"
18
"github.com/juju/juju/state"
19
statetesting "github.com/juju/juju/state/testing"
20
"github.com/juju/juju/testing"
23
type ActionSuite struct {
26
actionlessCharm *state.Charm
27
service *state.Application
28
actionlessService *state.Application
31
charmlessUnit *state.Unit
32
actionlessUnit *state.Unit
35
var _ = gc.Suite(&ActionSuite{})
37
func (s *ActionSuite) SetUpTest(c *gc.C) {
40
s.ConnSuite.SetUpTest(c)
42
s.charm = s.AddTestingCharm(c, "dummy")
43
s.actionlessCharm = s.AddTestingCharm(c, "actionless")
45
s.service = s.AddTestingService(c, "dummy", s.charm)
46
c.Assert(err, jc.ErrorIsNil)
47
s.actionlessService = s.AddTestingService(c, "actionless", s.actionlessCharm)
48
c.Assert(err, jc.ErrorIsNil)
50
sUrl, _ := s.service.CharmURL()
51
c.Assert(sUrl, gc.NotNil)
52
actionlessSUrl, _ := s.actionlessService.CharmURL()
53
c.Assert(actionlessSUrl, gc.NotNil)
55
s.unit, err = s.service.AddUnit()
56
c.Assert(err, jc.ErrorIsNil)
57
c.Assert(s.unit.Series(), gc.Equals, "quantal")
59
err = s.unit.SetCharmURL(sUrl)
60
c.Assert(err, jc.ErrorIsNil)
62
s.unit2, err = s.service.AddUnit()
63
c.Assert(err, jc.ErrorIsNil)
64
c.Assert(s.unit2.Series(), gc.Equals, "quantal")
66
err = s.unit2.SetCharmURL(sUrl)
67
c.Assert(err, jc.ErrorIsNil)
69
s.charmlessUnit, err = s.service.AddUnit()
70
c.Assert(err, jc.ErrorIsNil)
71
c.Assert(s.charmlessUnit.Series(), gc.Equals, "quantal")
73
s.actionlessUnit, err = s.actionlessService.AddUnit()
74
c.Assert(err, jc.ErrorIsNil)
75
c.Assert(s.actionlessUnit.Series(), gc.Equals, "quantal")
77
err = s.actionlessUnit.SetCharmURL(actionlessSUrl)
78
c.Assert(err, jc.ErrorIsNil)
81
func (s *ActionSuite) TestActionTag(c *gc.C) {
82
action, err := s.unit.AddAction("snapshot", nil)
83
c.Assert(err, jc.ErrorIsNil)
86
c.Assert(tag.String(), gc.Equals, "action-"+action.Id())
88
result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted})
89
c.Assert(err, jc.ErrorIsNil)
91
actions, err := s.unit.CompletedActions()
92
c.Assert(err, jc.ErrorIsNil)
93
c.Assert(len(actions), gc.Equals, 1)
95
actionResult := actions[0]
96
c.Assert(actionResult, gc.DeepEquals, result)
98
tag = actionResult.Tag()
99
c.Assert(tag.String(), gc.Equals, "action-"+actionResult.Id())
102
func (s *ActionSuite) TestAddAction(c *gc.C) {
103
for i, t := range []struct {
106
params map[string]interface{}
107
whichUnit *state.Unit
110
should: "enqueue normally",
113
//params: map[string]interface{}{"outfile": "outfile.tar.bz2"},
115
should: "fail on actionless charms",
117
whichUnit: s.actionlessUnit,
118
expectedErr: "no actions defined on charm \"local:quantal/quantal-actionless-1\"",
120
should: "fail on action not defined in schema",
122
name: "something-nonexistent",
123
expectedErr: "action \"something-nonexistent\" not defined on unit \"dummy/0\"",
125
should: "invalidate with bad params",
128
params: map[string]interface{}{
131
expectedErr: "validation failed: \\(root\\)\\.outfile : must be of type string, given 5",
133
c.Logf("Test %d: should %s", i, t.should)
134
before := state.NowToTheSecond()
135
later := before.Add(testing.LongWait)
137
// Copy params over into empty premade map for comparison later
138
params := make(map[string]interface{})
139
for k, v := range t.params {
143
// Verify we can add an Action
144
a, err := t.whichUnit.AddAction(t.name, params)
146
if t.expectedErr == "" {
147
c.Assert(err, jc.ErrorIsNil)
148
curl, _ := t.whichUnit.CharmURL()
149
ch, _ := s.State.Charm(curl)
150
schema := ch.Actions()
151
c.Logf("Schema for unit %q:\n%#v", t.whichUnit.Name(), schema)
152
// verify we can get it back out by Id
153
action, err := s.State.Action(a.Id())
154
c.Assert(err, jc.ErrorIsNil)
155
c.Assert(action, gc.NotNil)
156
c.Check(action.Id(), gc.Equals, a.Id())
158
// verify we get out what we put in
159
c.Check(action.Name(), gc.Equals, t.name)
160
c.Check(action.Parameters(), jc.DeepEquals, params)
162
// Enqueued time should be within a reasonable time of the beginning
164
now := state.NowToTheSecond()
165
c.Check(action.Enqueued(), jc.TimeBetween(before, now))
166
c.Check(action.Enqueued(), jc.TimeBetween(before, later))
170
c.Check(err, gc.ErrorMatches, t.expectedErr)
174
func (s *ActionSuite) TestAddActionInsertsDefaults(c *gc.C) {
175
units := make(map[string]*state.Unit)
176
schemas := map[string]string{
206
// Prepare the units for this test
207
makeUnits(c, s, units, schemas)
209
for i, t := range []struct {
211
params map[string]interface{}
213
expectedParams map[string]interface{}
215
should: "do nothing with no defaults",
216
params: map[string]interface{}{},
218
expectedParams: map[string]interface{}{},
220
should: "insert a simple default value",
221
params: map[string]interface{}{"foo": "bar"},
223
expectedParams: map[string]interface{}{
228
should: "insert a default value when an empty map is passed",
229
params: map[string]interface{}{},
231
expectedParams: map[string]interface{}{
235
should: "insert a default value when a nil map is passed",
238
expectedParams: map[string]interface{}{
242
should: "insert a nested default value",
243
params: map[string]interface{}{"foo": "bar"},
244
schema: "complicated",
245
expectedParams: map[string]interface{}{
247
"val": map[string]interface{}{
248
"bar": map[string]interface{}{
252
c.Logf("test %d: should %s", i, t.should)
254
// Note that AddAction will only result in errors in the case
255
// of malformed schemas, and schema objects can only be
256
// created from valid schemas. The error handling for this
257
// is tested in the gojsonschema package.
258
action, err := u.AddAction("act", t.params)
259
c.Assert(err, jc.ErrorIsNil)
260
c.Check(action.Parameters(), jc.DeepEquals, t.expectedParams)
264
// makeUnits prepares units with given Action schemas
265
func makeUnits(c *gc.C, s *ActionSuite, units map[string]*state.Unit, schemas map[string]string) {
266
// A few dummy charms that haven't been used yet
267
freeCharms := map[string]string{
269
"complicated": "mysql-alternative",
273
for name, schema := range schemas {
274
svcName := name + "-defaults-service"
276
// Add a testing service
277
ch := s.AddActionsCharm(c, freeCharms[name], schema, 1)
278
svc := s.AddTestingService(c, svcName, ch)
281
sUrl, _ := svc.CharmURL()
282
c.Assert(sUrl, gc.NotNil)
286
u, err := svc.AddUnit()
287
c.Assert(err, jc.ErrorIsNil)
288
c.Assert(u.Series(), gc.Equals, "quantal")
289
err = u.SetCharmURL(sUrl)
290
c.Assert(err, jc.ErrorIsNil)
296
func (s *ActionSuite) TestEnqueueActionRequiresName(c *gc.C) {
299
// verify can not enqueue an Action without a name
300
_, err := s.State.EnqueueAction(s.unit.Tag(), name, nil)
301
c.Assert(err, gc.ErrorMatches, "action name required")
304
func (s *ActionSuite) TestAddActionAcceptsDuplicateNames(c *gc.C) {
306
params1 := map[string]interface{}{"outfile": "outfile.tar.bz2"}
307
params2 := map[string]interface{}{"infile": "infile.zip"}
309
// verify can add two actions with same name
310
a1, err := s.unit.AddAction(name, params1)
311
c.Assert(err, jc.ErrorIsNil)
313
a2, err := s.unit.AddAction(name, params2)
314
c.Assert(err, jc.ErrorIsNil)
316
c.Assert(a1.Id(), gc.Not(gc.Equals), a2.Id())
318
// verify both actually got added
319
actions, err := s.unit.PendingActions()
320
c.Assert(err, jc.ErrorIsNil)
321
c.Assert(len(actions), gc.Equals, 2)
323
// verify we can Fail one, retrieve the other, and they're not mixed up
324
action1, err := s.State.Action(a1.Id())
325
c.Assert(err, jc.ErrorIsNil)
326
_, err = action1.Finish(state.ActionResults{Status: state.ActionFailed})
327
c.Assert(err, jc.ErrorIsNil)
329
action2, err := s.State.Action(a2.Id())
330
c.Assert(err, jc.ErrorIsNil)
331
c.Assert(action2.Parameters(), jc.DeepEquals, params2)
333
// verify only one left, and it's the expected one
334
actions, err = s.unit.PendingActions()
335
c.Assert(err, jc.ErrorIsNil)
336
c.Assert(len(actions), gc.Equals, 1)
337
c.Assert(actions[0].Id(), gc.Equals, a2.Id())
340
func (s *ActionSuite) TestAddActionLifecycle(c *gc.C) {
341
unit, err := s.State.Unit(s.unit.Name())
342
c.Assert(err, jc.ErrorIsNil)
343
preventUnitDestroyRemove(c, unit)
345
// make unit state Dying
347
c.Assert(err, jc.ErrorIsNil)
349
// can add action to a dying unit
350
_, err = unit.AddAction("snapshot", map[string]interface{}{})
351
c.Assert(err, jc.ErrorIsNil)
353
// make sure unit is dead
354
err = unit.EnsureDead()
355
c.Assert(err, jc.ErrorIsNil)
357
// cannot add action to a dead unit
358
_, err = unit.AddAction("snapshot", map[string]interface{}{})
359
c.Assert(err, gc.Equals, state.ErrDead)
362
func (s *ActionSuite) TestAddActionFailsOnDeadUnitInTransaction(c *gc.C) {
363
unit, err := s.State.Unit(s.unit.Name())
364
c.Assert(err, jc.ErrorIsNil)
365
preventUnitDestroyRemove(c, unit)
367
killUnit := txn.TestHook{
369
c.Assert(unit.Destroy(), gc.IsNil)
370
c.Assert(unit.EnsureDead(), gc.IsNil)
373
defer state.SetTestHooks(c, s.State, killUnit).Check()
375
_, err = unit.AddAction("snapshot", map[string]interface{}{})
376
c.Assert(err, gc.Equals, state.ErrDead)
379
func (s *ActionSuite) TestFail(c *gc.C) {
380
// get unit, add an action, retrieve that action
381
unit, err := s.State.Unit(s.unit.Name())
382
c.Assert(err, jc.ErrorIsNil)
383
preventUnitDestroyRemove(c, unit)
385
a, err := unit.AddAction("snapshot", nil)
386
c.Assert(err, jc.ErrorIsNil)
388
action, err := s.State.Action(a.Id())
389
c.Assert(err, jc.ErrorIsNil)
391
// ensure no action results for this action
392
results, err := unit.CompletedActions()
393
c.Assert(err, jc.ErrorIsNil)
394
c.Assert(len(results), gc.Equals, 0)
396
// fail the action, and verify that it succeeds
397
reason := "test fail reason"
398
result, err := action.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason})
399
c.Assert(err, jc.ErrorIsNil)
401
// ensure we now have a result for this action
402
results, err = unit.CompletedActions()
403
c.Assert(err, jc.ErrorIsNil)
404
c.Assert(len(results), gc.Equals, 1)
405
c.Assert(results[0], gc.DeepEquals, result)
407
c.Assert(results[0].Name(), gc.Equals, action.Name())
408
c.Assert(results[0].Status(), gc.Equals, state.ActionFailed)
410
// Verify the Action Completed time was within a reasonable
411
// time of the Enqueued time.
412
diff := results[0].Completed().Sub(action.Enqueued())
413
c.Assert(diff >= 0, jc.IsTrue)
414
c.Assert(diff < testing.LongWait, jc.IsTrue)
416
res, errstr := results[0].Results()
417
c.Assert(errstr, gc.Equals, reason)
418
c.Assert(res, gc.DeepEquals, map[string]interface{}{})
420
// validate that a pending action is no longer returned by UnitActions.
421
actions, err := unit.PendingActions()
422
c.Assert(err, jc.ErrorIsNil)
423
c.Assert(len(actions), gc.Equals, 0)
426
func (s *ActionSuite) TestComplete(c *gc.C) {
427
// get unit, add an action, retrieve that action
428
unit, err := s.State.Unit(s.unit.Name())
429
c.Assert(err, jc.ErrorIsNil)
430
preventUnitDestroyRemove(c, unit)
432
a, err := unit.AddAction("snapshot", nil)
433
c.Assert(err, jc.ErrorIsNil)
435
action, err := s.State.Action(a.Id())
436
c.Assert(err, jc.ErrorIsNil)
438
// ensure no action results for this action
439
results, err := unit.CompletedActions()
440
c.Assert(err, jc.ErrorIsNil)
441
c.Assert(len(results), gc.Equals, 0)
443
// complete the action, and verify that it succeeds
444
output := map[string]interface{}{"output": "action ran successfully"}
445
result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted, Results: output})
446
c.Assert(err, jc.ErrorIsNil)
448
// ensure we now have a result for this action
449
results, err = unit.CompletedActions()
450
c.Assert(err, jc.ErrorIsNil)
451
c.Assert(len(results), gc.Equals, 1)
452
c.Assert(results[0], gc.DeepEquals, result)
454
c.Assert(results[0].Name(), gc.Equals, action.Name())
455
c.Assert(results[0].Status(), gc.Equals, state.ActionCompleted)
456
res, errstr := results[0].Results()
457
c.Assert(errstr, gc.Equals, "")
458
c.Assert(res, gc.DeepEquals, output)
460
// validate that a pending action is no longer returned by UnitActions.
461
actions, err := unit.PendingActions()
462
c.Assert(err, jc.ErrorIsNil)
463
c.Assert(len(actions), gc.Equals, 0)
466
func (s *ActionSuite) TestFindActionTagsByPrefix(c *gc.C) {
468
uuidMock := uuidMockHelper{}
469
uuidMock.SetPrefixMask(prefix)
470
s.PatchValue(&state.NewUUID, uuidMock.NewUUID)
472
actions := []struct {
474
Parameters map[string]interface{}
476
{Name: "action-1", Parameters: map[string]interface{}{}},
477
{Name: "fake", Parameters: map[string]interface{}{"yeah": true, "take": nil}},
478
{Name: "action-9", Parameters: map[string]interface{}{"district": 9}},
479
{Name: "blarney", Parameters: map[string]interface{}{"conversation": []string{"what", "now"}}},
482
for _, action := range actions {
483
_, err := s.State.EnqueueAction(s.unit.Tag(), action.Name, action.Parameters)
484
c.Assert(err, gc.Equals, nil)
487
tags := s.State.FindActionTagsByPrefix(prefix)
489
c.Assert(len(tags), gc.Equals, len(actions))
490
for i, tag := range tags {
491
c.Logf("check %q against %d:%q", prefix, i, tag)
492
c.Check(tag.Id()[:len(prefix)], gc.Equals, prefix)
496
func (s *ActionSuite) TestFindActionsByName(c *gc.C) {
497
actions := []struct {
499
Parameters map[string]interface{}
501
{Name: "action-1", Parameters: map[string]interface{}{}},
502
{Name: "fake", Parameters: map[string]interface{}{"yeah": true, "take": nil}},
503
{Name: "action-1", Parameters: map[string]interface{}{"yeah": true, "take": nil}},
504
{Name: "action-9", Parameters: map[string]interface{}{"district": 9}},
505
{Name: "blarney", Parameters: map[string]interface{}{"conversation": []string{"what", "now"}}},
508
for _, action := range actions {
509
_, err := s.State.EnqueueAction(s.unit.Tag(), action.Name, action.Parameters)
510
c.Assert(err, gc.Equals, nil)
513
results, err := s.State.FindActionsByName("action-1")
514
c.Assert(err, jc.ErrorIsNil)
516
c.Assert(len(results), gc.Equals, 2)
517
for _, result := range results {
518
c.Check(result.Name(), gc.Equals, "action-1")
522
func (s *ActionSuite) TestActionsWatcherEmitsInitialChanges(c *gc.C) {
523
// LP-1391914 :: idPrefixWatcher fails watcher contract to send
524
// initial Change event
526
// state/idPrefixWatcher does not send an initial event in response
527
// to the first time Changes() is called if all of the pending
528
// events are removed before the first consumption of Changes().
529
// The watcher contract specifies that the first call to Changes()
530
// should always return at a minimum an empty change set to notify
531
// clients of it's initial state
534
svc := s.AddTestingService(c, "dummy3", s.charm)
535
unit, err := svc.AddUnit()
536
c.Assert(err, jc.ErrorIsNil)
537
u, err := s.State.Unit(unit.Name())
538
c.Assert(err, jc.ErrorIsNil)
539
preventUnitDestroyRemove(c, u)
542
a1, err := u.AddAction("snapshot", nil)
543
c.Assert(err, jc.ErrorIsNil)
544
a2, err := u.AddAction("snapshot", nil)
545
c.Assert(err, jc.ErrorIsNil)
547
// start watcher but don't consume Changes() yet
548
w := u.WatchActionNotifications()
549
defer statetesting.AssertStop(c, w)
550
wc := statetesting.NewStringsWatcherC(c, s.State, w)
554
_, err = a1.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason})
555
c.Assert(err, jc.ErrorIsNil)
556
_, err = a2.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason})
557
c.Assert(err, jc.ErrorIsNil)
559
// per contract, there should be at minimum an initial empty Change() result
560
wc.AssertChangeMaybeIncluding(expectActionIds(a1, a2)...)
564
func (s *ActionSuite) TestUnitWatchActionNotifications(c *gc.C) {
566
unit1, err := s.State.Unit(s.unit.Name())
567
c.Assert(err, jc.ErrorIsNil)
568
preventUnitDestroyRemove(c, unit1)
570
unit2, err := s.State.Unit(s.unit2.Name())
571
c.Assert(err, jc.ErrorIsNil)
572
preventUnitDestroyRemove(c, unit2)
574
// queue some actions before starting the watcher
575
fa1, err := unit1.AddAction("snapshot", nil)
576
c.Assert(err, jc.ErrorIsNil)
577
fa2, err := unit1.AddAction("snapshot", nil)
578
c.Assert(err, jc.ErrorIsNil)
580
// set up watcher on first unit
581
w := unit1.WatchActionNotifications()
582
defer statetesting.AssertStop(c, w)
583
wc := statetesting.NewStringsWatcherC(c, s.State, w)
584
// make sure the previously pending actions are sent on the watcher
585
expect := expectActionIds(fa1, fa2)
586
wc.AssertChange(expect...)
589
// add watcher on unit2
590
w2 := unit2.WatchActionNotifications()
591
defer statetesting.AssertStop(c, w2)
592
wc2 := statetesting.NewStringsWatcherC(c, s.State, w2)
596
// add action on unit2 and makes sure unit1 watcher doesn't trigger
597
// and unit2 watcher does
598
fa3, err := unit2.AddAction("snapshot", nil)
599
c.Assert(err, jc.ErrorIsNil)
601
expect2 := expectActionIds(fa3)
602
wc2.AssertChange(expect2...)
605
// add a couple actions on unit1 and make sure watcher sees events
606
fa4, err := unit1.AddAction("snapshot", nil)
607
c.Assert(err, jc.ErrorIsNil)
608
fa5, err := unit1.AddAction("snapshot", nil)
609
c.Assert(err, jc.ErrorIsNil)
611
expect = expectActionIds(fa4, fa5)
612
wc.AssertChange(expect...)
616
func (s *ActionSuite) TestMergeIds(c *gc.C) {
617
var tests = []struct {
623
{changes: "", adds: "a0,a1", removes: "", expected: "a0,a1"},
624
{changes: "a0,a1", adds: "", removes: "a0", expected: "a1"},
625
{changes: "a0,a1", adds: "a2", removes: "a0", expected: "a1,a2"},
627
{changes: "", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
628
{changes: "", adds: "a0,a1,a2", removes: "a0,a1,a2", expected: ""},
630
{changes: "a0", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
631
{changes: "a1", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
632
{changes: "a2", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
634
{changes: "a3,a4", adds: "a1,a4,a5", removes: "a1,a3", expected: "a4,a5"},
635
{changes: "a0,a1,a2", adds: "a1,a4,a5", removes: "a1,a3", expected: "a0,a2,a4,a5"},
638
prefix := state.DocID(s.State, "")
640
for ix, test := range tests {
641
updates := mapify(prefix, test.adds, test.removes)
642
changes := sliceify("", test.changes)
643
expected := sliceify("", test.expected)
645
c.Log(fmt.Sprintf("test number %d %#v", ix, test))
646
err := state.WatcherMergeIds(s.State, &changes, updates, state.ActionNotificationIdToActionId)
647
c.Assert(err, jc.ErrorIsNil)
648
c.Assert(changes, jc.SameContents, expected)
652
func (s *ActionSuite) TestMergeIdsErrors(c *gc.C) {
654
var tests = []struct {
658
{name: "bool", key: true},
659
{name: "int", key: 0},
660
{name: "chan string", key: make(chan string)},
663
for _, test := range tests {
664
changes, updates := []string{}, map[interface{}]bool{}
665
updates[test.key] = true
666
err := state.WatcherMergeIds(s.State, &changes, updates, state.ActionNotificationIdToActionId)
667
c.Assert(err, gc.ErrorMatches, "id is not of type string, got "+test.name)
671
func (s *ActionSuite) TestEnsureSuffix(c *gc.C) {
673
fn := state.WatcherEnsureSuffixFn(marker)
674
c.Assert(fn, gc.Not(gc.IsNil))
676
var tests = []struct {
680
{given: marker, expect: marker},
681
{given: "", expect: "" + marker},
682
{given: "asdf", expect: "asdf" + marker},
683
{given: "asdf" + marker, expect: "asdf" + marker},
684
{given: "asdf" + marker + "qwerty", expect: "asdf" + marker + "qwerty" + marker},
687
for _, test := range tests {
688
c.Assert(fn(test.given), gc.Equals, test.expect)
692
func (s *ActionSuite) TestMakeIdFilter(c *gc.C) {
695
fn := state.WatcherMakeIdFilter(s.State, marker)
696
c.Assert(fn, gc.IsNil)
698
ar1 := mockAR{id: "mock/1"}
699
ar2 := mockAR{id: "mock/2"}
700
fn = state.WatcherMakeIdFilter(s.State, marker, ar1, ar2)
701
c.Assert(fn, gc.Not(gc.IsNil))
703
var tests = []struct {
707
{id: "mock/1" + marker + "", match: true},
708
{id: "mock/1" + marker + "asdf", match: true},
709
{id: "mock/2" + marker + "", match: true},
710
{id: "mock/2" + marker + "asdf", match: true},
712
{id: "mock/1" + badmarker + "", match: false},
713
{id: "mock/1" + badmarker + "asdf", match: false},
714
{id: "mock/2" + badmarker + "", match: false},
715
{id: "mock/2" + badmarker + "asdf", match: false},
717
{id: "mock/1" + marker + "0", match: true},
718
{id: "mock/10" + marker + "0", match: false},
719
{id: "mock/2" + marker + "0", match: true},
720
{id: "mock/20" + marker + "0", match: false},
721
{id: "mock" + marker + "0", match: false},
723
{id: "" + marker + "0", match: false},
724
{id: "mock/1-0", match: false},
725
{id: "mock/1-0", match: false},
728
for _, test := range tests {
729
c.Assert(fn(state.DocID(s.State, test.id)), gc.Equals, test.match)
733
func (s *ActionSuite) TestWatchActionNotifications(c *gc.C) {
734
svc := s.AddTestingService(c, "dummy2", s.charm)
735
u, err := svc.AddUnit()
736
c.Assert(err, jc.ErrorIsNil)
738
w := u.WatchActionNotifications()
739
defer statetesting.AssertStop(c, w)
740
wc := statetesting.NewStringsWatcherC(c, s.State, w)
745
fa1, err := u.AddAction("snapshot", nil)
746
c.Assert(err, jc.ErrorIsNil)
747
fa2, err := u.AddAction("snapshot", nil)
748
c.Assert(err, jc.ErrorIsNil)
749
fa3, err := u.AddAction("snapshot", nil)
750
c.Assert(err, jc.ErrorIsNil)
752
// fail the middle one
753
action, err := s.State.Action(fa2.Id())
754
c.Assert(err, jc.ErrorIsNil)
755
_, err = action.Finish(state.ActionResults{Status: state.ActionFailed, Message: "die scum"})
756
c.Assert(err, jc.ErrorIsNil)
758
// expect the first and last one in the watcher
759
expect := expectActionIds(fa1, fa3)
760
wc.AssertChange(expect...)
764
func (s *ActionSuite) TestActionStatusWatcher(c *gc.C) {
765
testCase := []struct {
766
receiver state.ActionReceiver
768
status state.ActionStatus
770
{s.unit, "snapshot", state.ActionCancelled},
771
{s.unit2, "snapshot", state.ActionCancelled},
772
{s.unit, "snapshot", state.ActionPending},
773
{s.unit2, "snapshot", state.ActionPending},
774
{s.unit, "snapshot", state.ActionFailed},
775
{s.unit2, "snapshot", state.ActionFailed},
776
{s.unit, "snapshot", state.ActionCompleted},
777
{s.unit2, "snapshot", state.ActionCompleted},
780
w1 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit})
781
defer statetesting.AssertStop(c, w1)
783
w2 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit}, state.ActionFailed)
784
defer statetesting.AssertStop(c, w2)
786
w3 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit}, state.ActionCancelled, state.ActionCompleted)
787
defer statetesting.AssertStop(c, w3)
789
watchAny := statetesting.NewStringsWatcherC(c, s.State, w1)
790
watchAny.AssertChange()
791
watchAny.AssertNoChange()
793
watchFailed := statetesting.NewStringsWatcherC(c, s.State, w2)
794
watchFailed.AssertChange()
795
watchFailed.AssertNoChange()
797
watchCancelledOrCompleted := statetesting.NewStringsWatcherC(c, s.State, w3)
798
watchCancelledOrCompleted.AssertChange()
799
watchCancelledOrCompleted.AssertNoChange()
801
expect := map[state.ActionStatus][]state.Action{}
802
all := []state.Action{}
803
for _, tcase := range testCase {
804
a, err := tcase.receiver.AddAction(tcase.name, nil)
805
c.Assert(err, jc.ErrorIsNil)
807
action, err := s.State.Action(a.Id())
808
c.Assert(err, jc.ErrorIsNil)
810
_, err = action.Finish(state.ActionResults{Status: tcase.status})
811
c.Assert(err, jc.ErrorIsNil)
813
if tcase.receiver == s.unit {
814
expect[tcase.status] = append(expect[tcase.status], action)
815
all = append(all, action)
819
watchAny.AssertChange(expectActionIds(all...)...)
820
watchAny.AssertNoChange()
822
watchFailed.AssertChange(expectActionIds(expect[state.ActionFailed]...)...)
823
watchFailed.AssertNoChange()
825
cancelledAndCompleted := expectActionIds(append(expect[state.ActionCancelled], expect[state.ActionCompleted]...)...)
826
watchCancelledOrCompleted.AssertChange(cancelledAndCompleted...)
827
watchCancelledOrCompleted.AssertNoChange()
830
func expectActionIds(actions ...state.Action) []string {
831
ids := make([]string, len(actions))
832
for i, action := range actions {
838
// mapify is a convenience method, also to make reading the tests
839
// easier. It combines two comma delimited strings representing
840
// additions and removals and turns it into the map[interface{}]bool
842
func mapify(prefix, adds, removes string) map[interface{}]bool {
843
m := map[interface{}]bool{}
844
for _, v := range sliceify(prefix, adds) {
847
for _, v := range sliceify(prefix, removes) {
853
// sliceify turns a comma separated list of strings into a slice
854
// trimming white space and excluding empty strings.
855
func sliceify(prefix, csvlist string) []string {
860
for _, entry := range strings.Split(csvlist, ",") {
861
clean := strings.TrimSpace(entry)
863
slice = append(slice, prefix+clean)
869
// mockAR is an implementation of ActionReceiver that can be used for
870
// testing that requires the ActionReceiver.Tag() call to return a
876
var _ state.ActionReceiver = (*mockAR)(nil)
878
func (r mockAR) AddAction(name string, payload map[string]interface{}) (state.Action, error) {
881
func (r mockAR) CancelAction(state.Action) (state.Action, error) { return nil, nil }
882
func (r mockAR) WatchActionNotifications() state.StringsWatcher { return nil }
883
func (r mockAR) Actions() ([]state.Action, error) { return nil, nil }
884
func (r mockAR) CompletedActions() ([]state.Action, error) { return nil, nil }
885
func (r mockAR) PendingActions() ([]state.Action, error) { return nil, nil }
886
func (r mockAR) RunningActions() ([]state.Action, error) { return nil, nil }
887
func (r mockAR) Tag() names.Tag { return names.NewUnitTag(r.id) }
889
// TestMock verifies the mock UUID generator works as expected.
890
func (s *ActionSuite) TestMock(c *gc.C) {
892
uuidMock := uuidMockHelper{}
893
uuidMock.SetPrefixMask(prefix)
894
s.PatchValue(&state.NewUUID, uuidMock.NewUUID)
895
for i := 0; i < 10; i++ {
896
uuid, err := state.NewUUID()
897
c.Check(err, jc.ErrorIsNil)
898
c.Check(uuid.String()[:len(prefix)], gc.Equals, prefix)
902
type uuidGenFn func() (utils.UUID, error)
903
type uuidMockHelper struct {
908
func (h *uuidMockHelper) SetPrefixMask(prefix string) error {
909
prefix = strings.Replace(prefix, "-", "", 4)
910
mask, err := hex.DecodeString(prefix)
915
return errors.Errorf("prefix mask longer than uuid %q", prefix)
921
func (h *uuidMockHelper) NewUUID() (utils.UUID, error) {
922
uuidGenFn := h.original
923
if uuidGenFn == nil {
924
uuidGenFn = utils.NewUUID
926
uuid, err := uuidGenFn()
928
return uuid, errors.Trace(err)
930
return h.mask(uuid), nil
933
func (h *uuidMockHelper) mask(uuid utils.UUID) utils.UUID {
934
if len(h.prefixMask) > 0 {
935
for i, b := range h.prefixMask {