~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/state/action_test.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2014-2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package state_test
 
5
 
 
6
import (
 
7
        "encoding/hex"
 
8
        "fmt"
 
9
        "strings"
 
10
 
 
11
        "github.com/juju/errors"
 
12
        jc "github.com/juju/testing/checkers"
 
13
        "github.com/juju/txn"
 
14
        "github.com/juju/utils"
 
15
        gc "gopkg.in/check.v1"
 
16
        "gopkg.in/juju/names.v2"
 
17
 
 
18
        "github.com/juju/juju/state"
 
19
        statetesting "github.com/juju/juju/state/testing"
 
20
        "github.com/juju/juju/testing"
 
21
)
 
22
 
 
23
type ActionSuite struct {
 
24
        ConnSuite
 
25
        charm             *state.Charm
 
26
        actionlessCharm   *state.Charm
 
27
        service           *state.Application
 
28
        actionlessService *state.Application
 
29
        unit              *state.Unit
 
30
        unit2             *state.Unit
 
31
        charmlessUnit     *state.Unit
 
32
        actionlessUnit    *state.Unit
 
33
}
 
34
 
 
35
var _ = gc.Suite(&ActionSuite{})
 
36
 
 
37
func (s *ActionSuite) SetUpTest(c *gc.C) {
 
38
        var err error
 
39
 
 
40
        s.ConnSuite.SetUpTest(c)
 
41
 
 
42
        s.charm = s.AddTestingCharm(c, "dummy")
 
43
        s.actionlessCharm = s.AddTestingCharm(c, "actionless")
 
44
 
 
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)
 
49
 
 
50
        sUrl, _ := s.service.CharmURL()
 
51
        c.Assert(sUrl, gc.NotNil)
 
52
        actionlessSUrl, _ := s.actionlessService.CharmURL()
 
53
        c.Assert(actionlessSUrl, gc.NotNil)
 
54
 
 
55
        s.unit, err = s.service.AddUnit()
 
56
        c.Assert(err, jc.ErrorIsNil)
 
57
        c.Assert(s.unit.Series(), gc.Equals, "quantal")
 
58
 
 
59
        err = s.unit.SetCharmURL(sUrl)
 
60
        c.Assert(err, jc.ErrorIsNil)
 
61
 
 
62
        s.unit2, err = s.service.AddUnit()
 
63
        c.Assert(err, jc.ErrorIsNil)
 
64
        c.Assert(s.unit2.Series(), gc.Equals, "quantal")
 
65
 
 
66
        err = s.unit2.SetCharmURL(sUrl)
 
67
        c.Assert(err, jc.ErrorIsNil)
 
68
 
 
69
        s.charmlessUnit, err = s.service.AddUnit()
 
70
        c.Assert(err, jc.ErrorIsNil)
 
71
        c.Assert(s.charmlessUnit.Series(), gc.Equals, "quantal")
 
72
 
 
73
        s.actionlessUnit, err = s.actionlessService.AddUnit()
 
74
        c.Assert(err, jc.ErrorIsNil)
 
75
        c.Assert(s.actionlessUnit.Series(), gc.Equals, "quantal")
 
76
 
 
77
        err = s.actionlessUnit.SetCharmURL(actionlessSUrl)
 
78
        c.Assert(err, jc.ErrorIsNil)
 
79
}
 
80
 
 
81
func (s *ActionSuite) TestActionTag(c *gc.C) {
 
82
        action, err := s.unit.AddAction("snapshot", nil)
 
83
        c.Assert(err, jc.ErrorIsNil)
 
84
 
 
85
        tag := action.Tag()
 
86
        c.Assert(tag.String(), gc.Equals, "action-"+action.Id())
 
87
 
 
88
        result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted})
 
89
        c.Assert(err, jc.ErrorIsNil)
 
90
 
 
91
        actions, err := s.unit.CompletedActions()
 
92
        c.Assert(err, jc.ErrorIsNil)
 
93
        c.Assert(len(actions), gc.Equals, 1)
 
94
 
 
95
        actionResult := actions[0]
 
96
        c.Assert(actionResult, gc.DeepEquals, result)
 
97
 
 
98
        tag = actionResult.Tag()
 
99
        c.Assert(tag.String(), gc.Equals, "action-"+actionResult.Id())
 
100
}
 
101
 
 
102
func (s *ActionSuite) TestAddAction(c *gc.C) {
 
103
        for i, t := range []struct {
 
104
                should      string
 
105
                name        string
 
106
                params      map[string]interface{}
 
107
                whichUnit   *state.Unit
 
108
                expectedErr string
 
109
        }{{
 
110
                should:    "enqueue normally",
 
111
                name:      "snapshot",
 
112
                whichUnit: s.unit,
 
113
                //params:    map[string]interface{}{"outfile": "outfile.tar.bz2"},
 
114
        }, {
 
115
                should:      "fail on actionless charms",
 
116
                name:        "something",
 
117
                whichUnit:   s.actionlessUnit,
 
118
                expectedErr: "no actions defined on charm \"local:quantal/quantal-actionless-1\"",
 
119
        }, {
 
120
                should:      "fail on action not defined in schema",
 
121
                whichUnit:   s.unit,
 
122
                name:        "something-nonexistent",
 
123
                expectedErr: "action \"something-nonexistent\" not defined on unit \"dummy/0\"",
 
124
        }, {
 
125
                should:    "invalidate with bad params",
 
126
                whichUnit: s.unit,
 
127
                name:      "snapshot",
 
128
                params: map[string]interface{}{
 
129
                        "outfile": 5.0,
 
130
                },
 
131
                expectedErr: "validation failed: \\(root\\)\\.outfile : must be of type string, given 5",
 
132
        }} {
 
133
                c.Logf("Test %d: should %s", i, t.should)
 
134
                before := state.NowToTheSecond()
 
135
                later := before.Add(testing.LongWait)
 
136
 
 
137
                // Copy params over into empty premade map for comparison later
 
138
                params := make(map[string]interface{})
 
139
                for k, v := range t.params {
 
140
                        params[k] = v
 
141
                }
 
142
 
 
143
                // Verify we can add an Action
 
144
                a, err := t.whichUnit.AddAction(t.name, params)
 
145
 
 
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())
 
157
 
 
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)
 
161
 
 
162
                        // Enqueued time should be within a reasonable time of the beginning
 
163
                        // of the test
 
164
                        now := state.NowToTheSecond()
 
165
                        c.Check(action.Enqueued(), jc.TimeBetween(before, now))
 
166
                        c.Check(action.Enqueued(), jc.TimeBetween(before, later))
 
167
                        continue
 
168
                }
 
169
 
 
170
                c.Check(err, gc.ErrorMatches, t.expectedErr)
 
171
        }
 
172
}
 
173
 
 
174
func (s *ActionSuite) TestAddActionInsertsDefaults(c *gc.C) {
 
175
        units := make(map[string]*state.Unit)
 
176
        schemas := map[string]string{
 
177
                "simple": `
 
178
act:
 
179
  params:
 
180
    val:
 
181
      type: string
 
182
      default: somestr
 
183
`[1:],
 
184
                "complicated": `
 
185
act:
 
186
  params:
 
187
    val:
 
188
      type: object
 
189
      properties:
 
190
        foo:
 
191
          type: string
 
192
        bar:
 
193
          type: object
 
194
          properties:
 
195
            baz:
 
196
              type: string
 
197
              default: woz
 
198
`[1:],
 
199
                "none": `
 
200
act:
 
201
  params:
 
202
    val:
 
203
      type: string
 
204
`[1:]}
 
205
 
 
206
        // Prepare the units for this test
 
207
        makeUnits(c, s, units, schemas)
 
208
 
 
209
        for i, t := range []struct {
 
210
                should         string
 
211
                params         map[string]interface{}
 
212
                schema         string
 
213
                expectedParams map[string]interface{}
 
214
        }{{
 
215
                should:         "do nothing with no defaults",
 
216
                params:         map[string]interface{}{},
 
217
                schema:         "none",
 
218
                expectedParams: map[string]interface{}{},
 
219
        }, {
 
220
                should: "insert a simple default value",
 
221
                params: map[string]interface{}{"foo": "bar"},
 
222
                schema: "simple",
 
223
                expectedParams: map[string]interface{}{
 
224
                        "foo": "bar",
 
225
                        "val": "somestr",
 
226
                },
 
227
        }, {
 
228
                should: "insert a default value when an empty map is passed",
 
229
                params: map[string]interface{}{},
 
230
                schema: "simple",
 
231
                expectedParams: map[string]interface{}{
 
232
                        "val": "somestr",
 
233
                },
 
234
        }, {
 
235
                should: "insert a default value when a nil map is passed",
 
236
                params: nil,
 
237
                schema: "simple",
 
238
                expectedParams: map[string]interface{}{
 
239
                        "val": "somestr",
 
240
                },
 
241
        }, {
 
242
                should: "insert a nested default value",
 
243
                params: map[string]interface{}{"foo": "bar"},
 
244
                schema: "complicated",
 
245
                expectedParams: map[string]interface{}{
 
246
                        "foo": "bar",
 
247
                        "val": map[string]interface{}{
 
248
                                "bar": map[string]interface{}{
 
249
                                        "baz": "woz",
 
250
                                }}},
 
251
        }} {
 
252
                c.Logf("test %d: should %s", i, t.should)
 
253
                u := units[t.schema]
 
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)
 
261
        }
 
262
}
 
263
 
 
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{
 
268
                "simple":      "mysql",
 
269
                "complicated": "mysql-alternative",
 
270
                "none":        "wordpress",
 
271
        }
 
272
 
 
273
        for name, schema := range schemas {
 
274
                svcName := name + "-defaults-service"
 
275
 
 
276
                // Add a testing service
 
277
                ch := s.AddActionsCharm(c, freeCharms[name], schema, 1)
 
278
                svc := s.AddTestingService(c, svcName, ch)
 
279
 
 
280
                // Get its charm URL
 
281
                sUrl, _ := svc.CharmURL()
 
282
                c.Assert(sUrl, gc.NotNil)
 
283
 
 
284
                // Add a unit
 
285
                var err error
 
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)
 
291
 
 
292
                units[name] = u
 
293
        }
 
294
}
 
295
 
 
296
func (s *ActionSuite) TestEnqueueActionRequiresName(c *gc.C) {
 
297
        name := ""
 
298
 
 
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")
 
302
}
 
303
 
 
304
func (s *ActionSuite) TestAddActionAcceptsDuplicateNames(c *gc.C) {
 
305
        name := "snapshot"
 
306
        params1 := map[string]interface{}{"outfile": "outfile.tar.bz2"}
 
307
        params2 := map[string]interface{}{"infile": "infile.zip"}
 
308
 
 
309
        // verify can add two actions with same name
 
310
        a1, err := s.unit.AddAction(name, params1)
 
311
        c.Assert(err, jc.ErrorIsNil)
 
312
 
 
313
        a2, err := s.unit.AddAction(name, params2)
 
314
        c.Assert(err, jc.ErrorIsNil)
 
315
 
 
316
        c.Assert(a1.Id(), gc.Not(gc.Equals), a2.Id())
 
317
 
 
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)
 
322
 
 
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)
 
328
 
 
329
        action2, err := s.State.Action(a2.Id())
 
330
        c.Assert(err, jc.ErrorIsNil)
 
331
        c.Assert(action2.Parameters(), jc.DeepEquals, params2)
 
332
 
 
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())
 
338
}
 
339
 
 
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)
 
344
 
 
345
        // make unit state Dying
 
346
        err = unit.Destroy()
 
347
        c.Assert(err, jc.ErrorIsNil)
 
348
 
 
349
        // can add action to a dying unit
 
350
        _, err = unit.AddAction("snapshot", map[string]interface{}{})
 
351
        c.Assert(err, jc.ErrorIsNil)
 
352
 
 
353
        // make sure unit is dead
 
354
        err = unit.EnsureDead()
 
355
        c.Assert(err, jc.ErrorIsNil)
 
356
 
 
357
        // cannot add action to a dead unit
 
358
        _, err = unit.AddAction("snapshot", map[string]interface{}{})
 
359
        c.Assert(err, gc.Equals, state.ErrDead)
 
360
}
 
361
 
 
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)
 
366
 
 
367
        killUnit := txn.TestHook{
 
368
                Before: func() {
 
369
                        c.Assert(unit.Destroy(), gc.IsNil)
 
370
                        c.Assert(unit.EnsureDead(), gc.IsNil)
 
371
                },
 
372
        }
 
373
        defer state.SetTestHooks(c, s.State, killUnit).Check()
 
374
 
 
375
        _, err = unit.AddAction("snapshot", map[string]interface{}{})
 
376
        c.Assert(err, gc.Equals, state.ErrDead)
 
377
}
 
378
 
 
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)
 
384
 
 
385
        a, err := unit.AddAction("snapshot", nil)
 
386
        c.Assert(err, jc.ErrorIsNil)
 
387
 
 
388
        action, err := s.State.Action(a.Id())
 
389
        c.Assert(err, jc.ErrorIsNil)
 
390
 
 
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)
 
395
 
 
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)
 
400
 
 
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)
 
406
 
 
407
        c.Assert(results[0].Name(), gc.Equals, action.Name())
 
408
        c.Assert(results[0].Status(), gc.Equals, state.ActionFailed)
 
409
 
 
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)
 
415
 
 
416
        res, errstr := results[0].Results()
 
417
        c.Assert(errstr, gc.Equals, reason)
 
418
        c.Assert(res, gc.DeepEquals, map[string]interface{}{})
 
419
 
 
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)
 
424
}
 
425
 
 
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)
 
431
 
 
432
        a, err := unit.AddAction("snapshot", nil)
 
433
        c.Assert(err, jc.ErrorIsNil)
 
434
 
 
435
        action, err := s.State.Action(a.Id())
 
436
        c.Assert(err, jc.ErrorIsNil)
 
437
 
 
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)
 
442
 
 
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)
 
447
 
 
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)
 
453
 
 
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)
 
459
 
 
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)
 
464
}
 
465
 
 
466
func (s *ActionSuite) TestFindActionTagsByPrefix(c *gc.C) {
 
467
        prefix := "feedbeef"
 
468
        uuidMock := uuidMockHelper{}
 
469
        uuidMock.SetPrefixMask(prefix)
 
470
        s.PatchValue(&state.NewUUID, uuidMock.NewUUID)
 
471
 
 
472
        actions := []struct {
 
473
                Name       string
 
474
                Parameters map[string]interface{}
 
475
        }{
 
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"}}},
 
480
        }
 
481
 
 
482
        for _, action := range actions {
 
483
                _, err := s.State.EnqueueAction(s.unit.Tag(), action.Name, action.Parameters)
 
484
                c.Assert(err, gc.Equals, nil)
 
485
        }
 
486
 
 
487
        tags := s.State.FindActionTagsByPrefix(prefix)
 
488
 
 
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)
 
493
        }
 
494
}
 
495
 
 
496
func (s *ActionSuite) TestFindActionsByName(c *gc.C) {
 
497
        actions := []struct {
 
498
                Name       string
 
499
                Parameters map[string]interface{}
 
500
        }{
 
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"}}},
 
506
        }
 
507
 
 
508
        for _, action := range actions {
 
509
                _, err := s.State.EnqueueAction(s.unit.Tag(), action.Name, action.Parameters)
 
510
                c.Assert(err, gc.Equals, nil)
 
511
        }
 
512
 
 
513
        results, err := s.State.FindActionsByName("action-1")
 
514
        c.Assert(err, jc.ErrorIsNil)
 
515
 
 
516
        c.Assert(len(results), gc.Equals, 2)
 
517
        for _, result := range results {
 
518
                c.Check(result.Name(), gc.Equals, "action-1")
 
519
        }
 
520
}
 
521
 
 
522
func (s *ActionSuite) TestActionsWatcherEmitsInitialChanges(c *gc.C) {
 
523
        // LP-1391914 :: idPrefixWatcher fails watcher contract to send
 
524
        // initial Change event
 
525
        //
 
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
 
532
 
 
533
        // preamble
 
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)
 
540
 
 
541
        // queue up actions
 
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)
 
546
 
 
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)
 
551
 
 
552
        // remove actions
 
553
        reason := "removed"
 
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)
 
558
 
 
559
        // per contract, there should be at minimum an initial empty Change() result
 
560
        wc.AssertChangeMaybeIncluding(expectActionIds(a1, a2)...)
 
561
        wc.AssertNoChange()
 
562
}
 
563
 
 
564
func (s *ActionSuite) TestUnitWatchActionNotifications(c *gc.C) {
 
565
        // get units
 
566
        unit1, err := s.State.Unit(s.unit.Name())
 
567
        c.Assert(err, jc.ErrorIsNil)
 
568
        preventUnitDestroyRemove(c, unit1)
 
569
 
 
570
        unit2, err := s.State.Unit(s.unit2.Name())
 
571
        c.Assert(err, jc.ErrorIsNil)
 
572
        preventUnitDestroyRemove(c, unit2)
 
573
 
 
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)
 
579
 
 
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...)
 
587
        wc.AssertNoChange()
 
588
 
 
589
        // add watcher on unit2
 
590
        w2 := unit2.WatchActionNotifications()
 
591
        defer statetesting.AssertStop(c, w2)
 
592
        wc2 := statetesting.NewStringsWatcherC(c, s.State, w2)
 
593
        wc2.AssertChange()
 
594
        wc2.AssertNoChange()
 
595
 
 
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)
 
600
        wc.AssertNoChange()
 
601
        expect2 := expectActionIds(fa3)
 
602
        wc2.AssertChange(expect2...)
 
603
        wc2.AssertNoChange()
 
604
 
 
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)
 
610
 
 
611
        expect = expectActionIds(fa4, fa5)
 
612
        wc.AssertChange(expect...)
 
613
        wc.AssertNoChange()
 
614
}
 
615
 
 
616
func (s *ActionSuite) TestMergeIds(c *gc.C) {
 
617
        var tests = []struct {
 
618
                changes  string
 
619
                adds     string
 
620
                removes  string
 
621
                expected string
 
622
        }{
 
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"},
 
626
 
 
627
                {changes: "", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
 
628
                {changes: "", adds: "a0,a1,a2", removes: "a0,a1,a2", expected: ""},
 
629
 
 
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"},
 
633
 
 
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"},
 
636
        }
 
637
 
 
638
        prefix := state.DocID(s.State, "")
 
639
 
 
640
        for ix, test := range tests {
 
641
                updates := mapify(prefix, test.adds, test.removes)
 
642
                changes := sliceify("", test.changes)
 
643
                expected := sliceify("", test.expected)
 
644
 
 
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)
 
649
        }
 
650
}
 
651
 
 
652
func (s *ActionSuite) TestMergeIdsErrors(c *gc.C) {
 
653
 
 
654
        var tests = []struct {
 
655
                name string
 
656
                key  interface{}
 
657
        }{
 
658
                {name: "bool", key: true},
 
659
                {name: "int", key: 0},
 
660
                {name: "chan string", key: make(chan string)},
 
661
        }
 
662
 
 
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)
 
668
        }
 
669
}
 
670
 
 
671
func (s *ActionSuite) TestEnsureSuffix(c *gc.C) {
 
672
        marker := "-marker-"
 
673
        fn := state.WatcherEnsureSuffixFn(marker)
 
674
        c.Assert(fn, gc.Not(gc.IsNil))
 
675
 
 
676
        var tests = []struct {
 
677
                given  string
 
678
                expect string
 
679
        }{
 
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},
 
685
        }
 
686
 
 
687
        for _, test := range tests {
 
688
                c.Assert(fn(test.given), gc.Equals, test.expect)
 
689
        }
 
690
}
 
691
 
 
692
func (s *ActionSuite) TestMakeIdFilter(c *gc.C) {
 
693
        marker := "-marker-"
 
694
        badmarker := "-bad-"
 
695
        fn := state.WatcherMakeIdFilter(s.State, marker)
 
696
        c.Assert(fn, gc.IsNil)
 
697
 
 
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))
 
702
 
 
703
        var tests = []struct {
 
704
                id    string
 
705
                match bool
 
706
        }{
 
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},
 
711
 
 
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},
 
716
 
 
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},
 
722
 
 
723
                {id: "" + marker + "0", match: false},
 
724
                {id: "mock/1-0", match: false},
 
725
                {id: "mock/1-0", match: false},
 
726
        }
 
727
 
 
728
        for _, test := range tests {
 
729
                c.Assert(fn(state.DocID(s.State, test.id)), gc.Equals, test.match)
 
730
        }
 
731
}
 
732
 
 
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)
 
737
 
 
738
        w := u.WatchActionNotifications()
 
739
        defer statetesting.AssertStop(c, w)
 
740
        wc := statetesting.NewStringsWatcherC(c, s.State, w)
 
741
        wc.AssertChange()
 
742
        wc.AssertNoChange()
 
743
 
 
744
        // add 3 actions
 
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)
 
751
 
 
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)
 
757
 
 
758
        // expect the first and last one in the watcher
 
759
        expect := expectActionIds(fa1, fa3)
 
760
        wc.AssertChange(expect...)
 
761
        wc.AssertNoChange()
 
762
}
 
763
 
 
764
func (s *ActionSuite) TestActionStatusWatcher(c *gc.C) {
 
765
        testCase := []struct {
 
766
                receiver state.ActionReceiver
 
767
                name     string
 
768
                status   state.ActionStatus
 
769
        }{
 
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},
 
778
        }
 
779
 
 
780
        w1 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit})
 
781
        defer statetesting.AssertStop(c, w1)
 
782
 
 
783
        w2 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit}, state.ActionFailed)
 
784
        defer statetesting.AssertStop(c, w2)
 
785
 
 
786
        w3 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit}, state.ActionCancelled, state.ActionCompleted)
 
787
        defer statetesting.AssertStop(c, w3)
 
788
 
 
789
        watchAny := statetesting.NewStringsWatcherC(c, s.State, w1)
 
790
        watchAny.AssertChange()
 
791
        watchAny.AssertNoChange()
 
792
 
 
793
        watchFailed := statetesting.NewStringsWatcherC(c, s.State, w2)
 
794
        watchFailed.AssertChange()
 
795
        watchFailed.AssertNoChange()
 
796
 
 
797
        watchCancelledOrCompleted := statetesting.NewStringsWatcherC(c, s.State, w3)
 
798
        watchCancelledOrCompleted.AssertChange()
 
799
        watchCancelledOrCompleted.AssertNoChange()
 
800
 
 
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)
 
806
 
 
807
                action, err := s.State.Action(a.Id())
 
808
                c.Assert(err, jc.ErrorIsNil)
 
809
 
 
810
                _, err = action.Finish(state.ActionResults{Status: tcase.status})
 
811
                c.Assert(err, jc.ErrorIsNil)
 
812
 
 
813
                if tcase.receiver == s.unit {
 
814
                        expect[tcase.status] = append(expect[tcase.status], action)
 
815
                        all = append(all, action)
 
816
                }
 
817
        }
 
818
 
 
819
        watchAny.AssertChange(expectActionIds(all...)...)
 
820
        watchAny.AssertNoChange()
 
821
 
 
822
        watchFailed.AssertChange(expectActionIds(expect[state.ActionFailed]...)...)
 
823
        watchFailed.AssertNoChange()
 
824
 
 
825
        cancelledAndCompleted := expectActionIds(append(expect[state.ActionCancelled], expect[state.ActionCompleted]...)...)
 
826
        watchCancelledOrCompleted.AssertChange(cancelledAndCompleted...)
 
827
        watchCancelledOrCompleted.AssertNoChange()
 
828
}
 
829
 
 
830
func expectActionIds(actions ...state.Action) []string {
 
831
        ids := make([]string, len(actions))
 
832
        for i, action := range actions {
 
833
                ids[i] = action.Id()
 
834
        }
 
835
        return ids
 
836
}
 
837
 
 
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
 
841
// format needed
 
842
func mapify(prefix, adds, removes string) map[interface{}]bool {
 
843
        m := map[interface{}]bool{}
 
844
        for _, v := range sliceify(prefix, adds) {
 
845
                m[v] = true
 
846
        }
 
847
        for _, v := range sliceify(prefix, removes) {
 
848
                m[v] = false
 
849
        }
 
850
        return m
 
851
}
 
852
 
 
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 {
 
856
        slice := []string{}
 
857
        if csvlist == "" {
 
858
                return slice
 
859
        }
 
860
        for _, entry := range strings.Split(csvlist, ",") {
 
861
                clean := strings.TrimSpace(entry)
 
862
                if clean != "" {
 
863
                        slice = append(slice, prefix+clean)
 
864
                }
 
865
        }
 
866
        return slice
 
867
}
 
868
 
 
869
// mockAR is an implementation of ActionReceiver that can be used for
 
870
// testing that requires the ActionReceiver.Tag() call to return a
 
871
// names.Tag
 
872
type mockAR struct {
 
873
        id string
 
874
}
 
875
 
 
876
var _ state.ActionReceiver = (*mockAR)(nil)
 
877
 
 
878
func (r mockAR) AddAction(name string, payload map[string]interface{}) (state.Action, error) {
 
879
        return nil, nil
 
880
}
 
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) }
 
888
 
 
889
// TestMock verifies the mock UUID generator works as expected.
 
890
func (s *ActionSuite) TestMock(c *gc.C) {
 
891
        prefix := "abbadead"
 
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)
 
899
        }
 
900
}
 
901
 
 
902
type uuidGenFn func() (utils.UUID, error)
 
903
type uuidMockHelper struct {
 
904
        original   uuidGenFn
 
905
        prefixMask []byte
 
906
}
 
907
 
 
908
func (h *uuidMockHelper) SetPrefixMask(prefix string) error {
 
909
        prefix = strings.Replace(prefix, "-", "", 4)
 
910
        mask, err := hex.DecodeString(prefix)
 
911
        if err != nil {
 
912
                return err
 
913
        }
 
914
        if len(mask) > 16 {
 
915
                return errors.Errorf("prefix mask longer than uuid %q", prefix)
 
916
        }
 
917
        h.prefixMask = mask
 
918
        return nil
 
919
}
 
920
 
 
921
func (h *uuidMockHelper) NewUUID() (utils.UUID, error) {
 
922
        uuidGenFn := h.original
 
923
        if uuidGenFn == nil {
 
924
                uuidGenFn = utils.NewUUID
 
925
        }
 
926
        uuid, err := uuidGenFn()
 
927
        if err != nil {
 
928
                return uuid, errors.Trace(err)
 
929
        }
 
930
        return h.mask(uuid), nil
 
931
}
 
932
 
 
933
func (h *uuidMockHelper) mask(uuid utils.UUID) utils.UUID {
 
934
        if len(h.prefixMask) > 0 {
 
935
                for i, b := range h.prefixMask {
 
936
                        uuid[i] = b
 
937
                }
 
938
        }
 
939
        return uuid
 
940
}