~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/worker/uniter/storage/attachments_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 2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package storage_test
 
5
 
 
6
import (
 
7
        "io/ioutil"
 
8
        "path/filepath"
 
9
 
 
10
        "github.com/juju/errors"
 
11
        jc "github.com/juju/testing/checkers"
 
12
        "github.com/juju/utils/set"
 
13
        gc "gopkg.in/check.v1"
 
14
        "gopkg.in/juju/charm.v6-unstable/hooks"
 
15
        "gopkg.in/juju/names.v2"
 
16
 
 
17
        "github.com/juju/juju/apiserver/params"
 
18
        corestorage "github.com/juju/juju/storage"
 
19
        "github.com/juju/juju/testing"
 
20
        "github.com/juju/juju/worker/uniter/hook"
 
21
        "github.com/juju/juju/worker/uniter/operation"
 
22
        "github.com/juju/juju/worker/uniter/remotestate"
 
23
        "github.com/juju/juju/worker/uniter/resolver"
 
24
        "github.com/juju/juju/worker/uniter/storage"
 
25
)
 
26
 
 
27
type attachmentsSuite struct {
 
28
        testing.BaseSuite
 
29
}
 
30
 
 
31
var _ = gc.Suite(&attachmentsSuite{})
 
32
 
 
33
func assertStorageTags(c *gc.C, a *storage.Attachments, tags ...names.StorageTag) {
 
34
        sTags, err := a.StorageTags()
 
35
        c.Assert(err, jc.ErrorIsNil)
 
36
        c.Assert(sTags, jc.SameContents, tags)
 
37
}
 
38
 
 
39
func (s *attachmentsSuite) TestNewAttachments(c *gc.C) {
 
40
        stateDir := filepath.Join(c.MkDir(), "nonexistent")
 
41
        unitTag := names.NewUnitTag("mysql/0")
 
42
        abort := make(chan struct{})
 
43
        st := &mockStorageAccessor{
 
44
                unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
 
45
                        c.Assert(u, gc.Equals, unitTag)
 
46
                        return nil, nil
 
47
                },
 
48
        }
 
49
 
 
50
        _, err := storage.NewAttachments(st, unitTag, stateDir, abort)
 
51
        c.Assert(err, jc.ErrorIsNil)
 
52
        // state dir should have been created.
 
53
        c.Assert(stateDir, jc.IsDirectory)
 
54
}
 
55
 
 
56
func (s *attachmentsSuite) TestNewAttachmentsInit(c *gc.C) {
 
57
        stateDir := c.MkDir()
 
58
        unitTag := names.NewUnitTag("mysql/0")
 
59
        abort := make(chan struct{})
 
60
 
 
61
        // Simulate remote state returning a single Alive storage attachment.
 
62
        storageTag := names.NewStorageTag("data/0")
 
63
        attachmentIds := []params.StorageAttachmentId{{
 
64
                StorageTag: storageTag.String(),
 
65
                UnitTag:    unitTag.String(),
 
66
        }}
 
67
        attachment := params.StorageAttachment{
 
68
                StorageTag: storageTag.String(),
 
69
                UnitTag:    unitTag.String(),
 
70
                Life:       params.Alive,
 
71
                Kind:       params.StorageKindBlock,
 
72
                Location:   "/dev/sdb",
 
73
        }
 
74
 
 
75
        st := &mockStorageAccessor{
 
76
                unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
 
77
                        c.Assert(u, gc.Equals, unitTag)
 
78
                        return attachmentIds, nil
 
79
                },
 
80
                storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
 
81
                        c.Assert(s, gc.Equals, storageTag)
 
82
                        return attachment, nil
 
83
                },
 
84
        }
 
85
 
 
86
        withAttachments := func(f func(*storage.Attachments)) {
 
87
                att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
 
88
                c.Assert(err, jc.ErrorIsNil)
 
89
                f(att)
 
90
        }
 
91
 
 
92
        // No state files, so no storagers will be started.
 
93
        var called int
 
94
        withAttachments(func(att *storage.Attachments) {
 
95
                called++
 
96
                c.Assert(att.Pending(), gc.Equals, 1)
 
97
                err := att.ValidateHook(hook.Info{
 
98
                        Kind:      hooks.StorageAttached,
 
99
                        StorageId: storageTag.Id(),
 
100
                })
 
101
                c.Assert(err, gc.ErrorMatches, `unknown storage "data/0"`)
 
102
                assertStorageTags(c, att) // no active attachment
 
103
        })
 
104
        c.Assert(called, gc.Equals, 1)
 
105
 
 
106
        // Commit a storage-attached to local state and try again.
 
107
        state0, err := storage.ReadStateFile(stateDir, storageTag)
 
108
        c.Assert(err, jc.ErrorIsNil)
 
109
        err = state0.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/0"})
 
110
        c.Assert(err, jc.ErrorIsNil)
 
111
        // Create an extra one so we can make sure it gets removed.
 
112
        state1, err := storage.ReadStateFile(stateDir, names.NewStorageTag("data/1"))
 
113
        c.Assert(err, jc.ErrorIsNil)
 
114
        err = state1.CommitHook(hook.Info{Kind: hooks.StorageAttached, StorageId: "data/1"})
 
115
        c.Assert(err, jc.ErrorIsNil)
 
116
 
 
117
        withAttachments(func(att *storage.Attachments) {
 
118
                // We should be able to get the initial storage context
 
119
                // for existing storage immediately, without having to
 
120
                // wait for any hooks to fire.
 
121
                ctx, err := att.Storage(storageTag)
 
122
                c.Assert(err, jc.ErrorIsNil)
 
123
                c.Assert(ctx, gc.NotNil)
 
124
                c.Assert(ctx.Tag(), gc.Equals, storageTag)
 
125
                c.Assert(ctx.Tag(), gc.Equals, storageTag)
 
126
                c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindBlock)
 
127
                c.Assert(ctx.Location(), gc.Equals, "/dev/sdb")
 
128
 
 
129
                called++
 
130
                c.Assert(att.Pending(), gc.Equals, 0)
 
131
                err = att.ValidateHook(hook.Info{
 
132
                        Kind:      hooks.StorageDetaching,
 
133
                        StorageId: storageTag.Id(),
 
134
                })
 
135
                c.Assert(err, jc.ErrorIsNil)
 
136
                err = att.ValidateHook(hook.Info{
 
137
                        Kind:      hooks.StorageAttached,
 
138
                        StorageId: "data/1",
 
139
                })
 
140
                c.Assert(err, gc.ErrorMatches, `unknown storage "data/1"`)
 
141
                assertStorageTags(c, att, storageTag)
 
142
        })
 
143
        c.Assert(called, gc.Equals, 2)
 
144
        c.Assert(filepath.Join(stateDir, "data-0"), jc.IsNonEmptyFile)
 
145
        c.Assert(filepath.Join(stateDir, "data-1"), jc.DoesNotExist)
 
146
}
 
147
 
 
148
func (s *attachmentsSuite) TestAttachmentsUpdateShortCircuitDeath(c *gc.C) {
 
149
        stateDir := c.MkDir()
 
150
        unitTag := names.NewUnitTag("mysql/0")
 
151
        abort := make(chan struct{})
 
152
 
 
153
        storageTag0 := names.NewStorageTag("data/0")
 
154
        storageTag1 := names.NewStorageTag("data/1")
 
155
 
 
156
        removed := set.NewTags()
 
157
        st := &mockStorageAccessor{
 
158
                unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
 
159
                        return nil, nil
 
160
                },
 
161
                remove: func(s names.StorageTag, u names.UnitTag) error {
 
162
                        c.Assert(u, gc.Equals, unitTag)
 
163
                        removed.Add(s)
 
164
                        return nil
 
165
                },
 
166
        }
 
167
 
 
168
        att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
 
169
        c.Assert(err, jc.ErrorIsNil)
 
170
        r := storage.NewResolver(att)
 
171
 
 
172
        // First make sure we create a storage-attached hook operation for
 
173
        // data/0. We do this to show that until the hook is *committed*,
 
174
        // we will still short-circuit removal.
 
175
        localState := resolver.LocalState{State: operation.State{
 
176
                Kind: operation.Continue,
 
177
        }}
 
178
        _, err = r.NextOp(localState, remotestate.Snapshot{
 
179
                Life: params.Alive,
 
180
                Storage: map[names.StorageTag]remotestate.StorageSnapshot{
 
181
                        storageTag0: {
 
182
                                Life:     params.Alive,
 
183
                                Kind:     params.StorageKindBlock,
 
184
                                Location: "/dev/sdb",
 
185
                                Attached: true,
 
186
                        },
 
187
                },
 
188
        }, &mockOperations{})
 
189
        c.Assert(err, jc.ErrorIsNil)
 
190
 
 
191
        for _, storageTag := range []names.StorageTag{storageTag0, storageTag1} {
 
192
                _, err = r.NextOp(localState, remotestate.Snapshot{
 
193
                        Life: params.Alive,
 
194
                        Storage: map[names.StorageTag]remotestate.StorageSnapshot{
 
195
                                storageTag: {Life: params.Dying},
 
196
                        },
 
197
                }, nil)
 
198
                c.Assert(err, gc.Equals, resolver.ErrNoOperation)
 
199
        }
 
200
        c.Assert(removed.SortedValues(), jc.DeepEquals, []names.Tag{
 
201
                storageTag0, storageTag1,
 
202
        })
 
203
}
 
204
 
 
205
func (s *attachmentsSuite) TestAttachmentsStorage(c *gc.C) {
 
206
        stateDir := c.MkDir()
 
207
        unitTag := names.NewUnitTag("mysql/0")
 
208
        abort := make(chan struct{})
 
209
 
 
210
        st := &mockStorageAccessor{
 
211
                unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
 
212
                        return nil, nil
 
213
                },
 
214
        }
 
215
 
 
216
        att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
 
217
        c.Assert(err, jc.ErrorIsNil)
 
218
        r := storage.NewResolver(att)
 
219
 
 
220
        storageTag := names.NewStorageTag("data/0")
 
221
        _, err = att.Storage(storageTag)
 
222
        c.Assert(err, jc.Satisfies, errors.IsNotFound)
 
223
        assertStorageTags(c, att)
 
224
 
 
225
        // Inform the resolver of an attachment.
 
226
        localState := resolver.LocalState{State: operation.State{
 
227
                Kind: operation.Continue,
 
228
        }}
 
229
        op, err := r.NextOp(localState, remotestate.Snapshot{
 
230
                Life: params.Alive,
 
231
                Storage: map[names.StorageTag]remotestate.StorageSnapshot{
 
232
                        storageTag: {
 
233
                                Kind:     params.StorageKindBlock,
 
234
                                Life:     params.Alive,
 
235
                                Location: "/dev/sdb",
 
236
                                Attached: true,
 
237
                        },
 
238
                },
 
239
        }, &mockOperations{})
 
240
        c.Assert(err, jc.ErrorIsNil)
 
241
        c.Assert(op.String(), gc.Equals, "run hook storage-attached")
 
242
        assertStorageTags(c, att, storageTag)
 
243
 
 
244
        ctx, err := att.Storage(storageTag)
 
245
        c.Assert(err, jc.ErrorIsNil)
 
246
        c.Assert(ctx, gc.NotNil)
 
247
        c.Assert(ctx.Tag(), gc.Equals, storageTag)
 
248
        c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindBlock)
 
249
        c.Assert(ctx.Location(), gc.Equals, "/dev/sdb")
 
250
}
 
251
 
 
252
func (s *attachmentsSuite) TestAttachmentsCommitHook(c *gc.C) {
 
253
        stateDir := c.MkDir()
 
254
        unitTag := names.NewUnitTag("mysql/0")
 
255
        abort := make(chan struct{})
 
256
 
 
257
        var removed bool
 
258
        storageTag := names.NewStorageTag("data/0")
 
259
        st := &mockStorageAccessor{
 
260
                unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
 
261
                        return nil, nil
 
262
                },
 
263
                remove: func(s names.StorageTag, u names.UnitTag) error {
 
264
                        removed = true
 
265
                        c.Assert(s, gc.Equals, storageTag)
 
266
                        return nil
 
267
                },
 
268
        }
 
269
 
 
270
        att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
 
271
        c.Assert(err, jc.ErrorIsNil)
 
272
        r := storage.NewResolver(att)
 
273
 
 
274
        // Inform the resolver of an attachment.
 
275
        localState := resolver.LocalState{State: operation.State{
 
276
                Kind: operation.Continue,
 
277
        }}
 
278
        _, err = r.NextOp(localState, remotestate.Snapshot{
 
279
                Life: params.Alive,
 
280
                Storage: map[names.StorageTag]remotestate.StorageSnapshot{
 
281
                        storageTag: {
 
282
                                Kind:     params.StorageKindBlock,
 
283
                                Life:     params.Alive,
 
284
                                Location: "/dev/sdb",
 
285
                                Attached: true,
 
286
                        },
 
287
                },
 
288
        }, &mockOperations{})
 
289
        c.Assert(err, jc.ErrorIsNil)
 
290
        c.Assert(att.Pending(), gc.Equals, 1)
 
291
 
 
292
        // No file exists until storage-attached is committed.
 
293
        stateFile := filepath.Join(stateDir, "data-0")
 
294
        c.Assert(stateFile, jc.DoesNotExist)
 
295
 
 
296
        err = att.CommitHook(hook.Info{
 
297
                Kind:      hooks.StorageAttached,
 
298
                StorageId: storageTag.Id(),
 
299
        })
 
300
        c.Assert(err, jc.ErrorIsNil)
 
301
        data, err := ioutil.ReadFile(stateFile)
 
302
        c.Assert(err, jc.ErrorIsNil)
 
303
        c.Assert(string(data), gc.Equals, "attached: true\n")
 
304
        c.Assert(att.Pending(), gc.Equals, 0)
 
305
 
 
306
        c.Assert(removed, jc.IsFalse)
 
307
        err = att.CommitHook(hook.Info{
 
308
                Kind:      hooks.StorageDetaching,
 
309
                StorageId: storageTag.Id(),
 
310
        })
 
311
        c.Assert(err, jc.ErrorIsNil)
 
312
        c.Assert(stateFile, jc.DoesNotExist)
 
313
        c.Assert(removed, jc.IsTrue)
 
314
}
 
315
 
 
316
func (s *attachmentsSuite) TestAttachmentsSetDying(c *gc.C) {
 
317
        stateDir := c.MkDir()
 
318
        unitTag := names.NewUnitTag("mysql/0")
 
319
        abort := make(chan struct{})
 
320
 
 
321
        storageTag := names.NewStorageTag("data/0")
 
322
        var destroyed, removed bool
 
323
        st := &mockStorageAccessor{
 
324
                unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
 
325
                        c.Assert(u, gc.Equals, unitTag)
 
326
                        return []params.StorageAttachmentId{{
 
327
                                StorageTag: storageTag.String(),
 
328
                                UnitTag:    unitTag.String(),
 
329
                        }}, nil
 
330
                },
 
331
                storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) {
 
332
                        c.Assert(u, gc.Equals, unitTag)
 
333
                        c.Assert(s, gc.Equals, storageTag)
 
334
                        return params.StorageAttachment{}, &params.Error{
 
335
                                Message: "not provisioned",
 
336
                                Code:    params.CodeNotProvisioned,
 
337
                        }
 
338
                },
 
339
                destroyUnitStorageAttachments: func(u names.UnitTag) error {
 
340
                        c.Assert(u, gc.Equals, unitTag)
 
341
                        destroyed = true
 
342
                        return nil
 
343
                },
 
344
                remove: func(s names.StorageTag, u names.UnitTag) error {
 
345
                        c.Assert(removed, jc.IsFalse)
 
346
                        c.Assert(s, gc.Equals, storageTag)
 
347
                        c.Assert(u, gc.Equals, unitTag)
 
348
                        removed = true
 
349
                        return nil
 
350
                },
 
351
        }
 
352
 
 
353
        att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
 
354
        c.Assert(err, jc.ErrorIsNil)
 
355
        c.Assert(att.Pending(), gc.Equals, 1)
 
356
        r := storage.NewResolver(att)
 
357
 
 
358
        // Inform the resolver that the unit is Dying. The storage is still
 
359
        // Alive, and is now provisioned, but will be destroyed and removed
 
360
        // by the resolver.
 
361
        localState := resolver.LocalState{State: operation.State{
 
362
                Kind: operation.Continue,
 
363
        }}
 
364
        _, err = r.NextOp(localState, remotestate.Snapshot{
 
365
                Life: params.Dying,
 
366
                Storage: map[names.StorageTag]remotestate.StorageSnapshot{
 
367
                        storageTag: {
 
368
                                Kind:     params.StorageKindBlock,
 
369
                                Life:     params.Alive,
 
370
                                Location: "/dev/sdb",
 
371
                                Attached: true,
 
372
                        },
 
373
                },
 
374
        }, &mockOperations{})
 
375
        c.Assert(err, gc.Equals, resolver.ErrNoOperation)
 
376
        c.Assert(destroyed, jc.IsTrue)
 
377
        c.Assert(att.Pending(), gc.Equals, 0)
 
378
        c.Assert(removed, jc.IsTrue)
 
379
}
 
380
 
 
381
func (s *attachmentsSuite) TestAttachmentsWaitPending(c *gc.C) {
 
382
        stateDir := c.MkDir()
 
383
        unitTag := names.NewUnitTag("mysql/0")
 
384
        abort := make(chan struct{})
 
385
 
 
386
        storageTag := names.NewStorageTag("data/0")
 
387
        st := &mockStorageAccessor{
 
388
                unitStorageAttachments: func(u names.UnitTag) ([]params.StorageAttachmentId, error) {
 
389
                        return nil, nil
 
390
                },
 
391
        }
 
392
 
 
393
        att, err := storage.NewAttachments(st, unitTag, stateDir, abort)
 
394
        c.Assert(err, jc.ErrorIsNil)
 
395
        r := storage.NewResolver(att)
 
396
 
 
397
        nextOp := func(installed bool) error {
 
398
                localState := resolver.LocalState{State: operation.State{
 
399
                        Installed: installed,
 
400
                        Kind:      operation.Continue,
 
401
                }}
 
402
                _, err := r.NextOp(localState, remotestate.Snapshot{
 
403
                        Life: params.Alive,
 
404
                        Storage: map[names.StorageTag]remotestate.StorageSnapshot{
 
405
                                storageTag: {
 
406
                                        Life:     params.Alive,
 
407
                                        Attached: false,
 
408
                                },
 
409
                        },
 
410
                }, &mockOperations{})
 
411
                return err
 
412
        }
 
413
 
 
414
        // Inform the resolver of a new, unprovisioned storage attachment.
 
415
        // Before install, we should wait for its completion; after install,
 
416
        // we should not.
 
417
        err = nextOp(false /* workload not installed */)
 
418
        c.Assert(att.Pending(), gc.Equals, 1)
 
419
        c.Assert(err, gc.Equals, resolver.ErrWaiting)
 
420
 
 
421
        err = nextOp(true /* workload installed */)
 
422
        c.Assert(err, gc.Equals, resolver.ErrNoOperation)
 
423
}