github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/storage/source_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storage_test 5 6 import ( 7 "time" 8 9 "github.com/juju/names" 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/juju/charm.v5/hooks" 13 14 "github.com/juju/juju/api/watcher" 15 "github.com/juju/juju/apiserver/params" 16 corestorage "github.com/juju/juju/storage" 17 "github.com/juju/juju/testing" 18 "github.com/juju/juju/worker/uniter/hook" 19 "github.com/juju/juju/worker/uniter/storage" 20 ) 21 22 const initiallyUnattached = false 23 const initiallyAttached = true 24 25 type storageHookQueueSuite struct { 26 testing.BaseSuite 27 } 28 29 var _ = gc.Suite(&storageHookQueueSuite{}) 30 31 func newHookQueue(attached bool) storage.StorageHookQueue { 32 return storage.NewStorageHookQueue( 33 names.NewUnitTag("mysql/0"), 34 names.NewStorageTag("data/0"), 35 attached, 36 ) 37 } 38 39 func updateHookQueue(c *gc.C, q storage.StorageHookQueue, life params.Life) { 40 err := q.Update(params.StorageAttachment{ 41 Life: life, 42 Kind: params.StorageKindBlock, 43 Location: "/dev/sdb", 44 }) 45 c.Assert(err, jc.ErrorIsNil) 46 } 47 48 func (s *storageHookQueueSuite) TestStorageHookQueueAttachedHook(c *gc.C) { 49 q := newHookQueue(initiallyUnattached) 50 updateHookQueue(c, q, params.Alive) 51 c.Assert(q.Empty(), jc.IsFalse) 52 c.Assert(q.Next(), gc.Equals, hook.Info{ 53 Kind: hooks.StorageAttached, 54 StorageId: "data/0", 55 }) 56 } 57 58 func (s *storageHookQueueSuite) TestStorageHookQueueAlreadyAttached(c *gc.C) { 59 q := newHookQueue(initiallyAttached) 60 updateHookQueue(c, q, params.Alive) 61 // Already attached, so no hooks should have been queued. 62 c.Assert(q.Empty(), jc.IsTrue) 63 } 64 65 func (s *storageHookQueueSuite) TestStorageHookQueueAttachedDetach(c *gc.C) { 66 q := newHookQueue(initiallyAttached) 67 updateHookQueue(c, q, params.Dying) 68 c.Assert(q.Empty(), jc.IsFalse) 69 c.Assert(q.Next(), gc.Equals, hook.Info{ 70 Kind: hooks.StorageDetaching, 71 StorageId: "data/0", 72 }) 73 } 74 75 func (s *storageHookQueueSuite) TestStorageHookQueueUnattachedDetach(c *gc.C) { 76 q := newHookQueue(initiallyUnattached) 77 updateHookQueue(c, q, params.Dying) 78 // the storage wasn't attached, so Dying short-circuits. 79 c.Assert(q.Empty(), jc.IsTrue) 80 } 81 82 func (s *storageHookQueueSuite) TestStorageHookQueueAttachedUnconsumedDetach(c *gc.C) { 83 q := newHookQueue(initiallyUnattached) 84 updateHookQueue(c, q, params.Alive) 85 c.Assert(q.Next(), gc.Equals, hook.Info{ 86 Kind: hooks.StorageAttached, 87 StorageId: "data/0", 88 }) 89 // don't consume the storage-attached hook; it should then be unqueued 90 updateHookQueue(c, q, params.Dying) 91 // since the storage-attached hook wasn't consumed, Dying short-circuits. 92 c.Assert(q.Empty(), jc.IsTrue) 93 } 94 95 func (s *storageHookQueueSuite) TestStorageHookQueueAttachDetach(c *gc.C) { 96 q := newHookQueue(initiallyUnattached) 97 updateHookQueue(c, q, params.Alive) 98 q.Pop() 99 updateHookQueue(c, q, params.Dying) 100 c.Assert(q.Empty(), jc.IsFalse) 101 c.Assert(q.Next(), gc.Equals, hook.Info{ 102 Kind: hooks.StorageDetaching, 103 StorageId: "data/0", 104 }) 105 } 106 107 func (s *storageHookQueueSuite) TestStorageHookQueueDead(c *gc.C) { 108 q := newHookQueue(initiallyAttached) 109 updateHookQueue(c, q, params.Dying) 110 q.Pop() 111 updateHookQueue(c, q, params.Dead) 112 // Dead does not cause any hook to be queued. 113 c.Assert(q.Empty(), jc.IsTrue) 114 } 115 116 func (s *storageHookQueueSuite) TestStorageHookQueueContext(c *gc.C) { 117 q := newHookQueue(initiallyUnattached) 118 _, ok := q.Context() 119 c.Assert(ok, jc.IsFalse) 120 121 err := q.Update(params.StorageAttachment{ 122 Life: params.Alive, 123 Kind: params.StorageKindFilesystem, 124 Location: "/srv", 125 }) 126 c.Assert(err, jc.ErrorIsNil) 127 c.Assert(q.Empty(), jc.IsFalse) 128 129 ctx, ok := q.Context() 130 c.Assert(ok, jc.IsTrue) 131 c.Assert(ctx, gc.NotNil) 132 c.Assert(ctx.Tag(), gc.Equals, names.NewStorageTag("data/0")) 133 c.Assert(ctx.Kind(), gc.Equals, corestorage.StorageKindFilesystem) 134 c.Assert(ctx.Location(), gc.Equals, "/srv") 135 } 136 137 func (s *storageHookQueueSuite) TestStorageHookQueueEmpty(c *gc.C) { 138 q := newHookQueue(initiallyAttached) 139 c.Assert(q.Empty(), jc.IsTrue) 140 c.Assert(q.Next, gc.PanicMatches, "source is empty") 141 c.Assert(q.Pop, gc.PanicMatches, "source is empty") 142 } 143 144 func (s *storageHookQueueSuite) TestStorageSourceStop(c *gc.C) { 145 unitTag := names.NewUnitTag("mysql/0") 146 storageTag := names.NewStorageTag("data/0") 147 148 // Simulate remote state returning a single Alive storage attachment. 149 st := &mockStorageAccessor{ 150 watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) { 151 return newMockNotifyWatcher(), nil 152 }, 153 } 154 155 const initiallyUnattached = false 156 source, err := storage.NewStorageSource(st, unitTag, storageTag, initiallyUnattached) 157 c.Assert(err, jc.ErrorIsNil) 158 err = source.Stop() 159 c.Assert(err, jc.ErrorIsNil) 160 } 161 162 func (s *storageHookQueueSuite) TestStorageSourceUpdateErrors(c *gc.C) { 163 unitTag := names.NewUnitTag("mysql/0") 164 storageTag := names.NewStorageTag("data/0") 165 166 // Simulate remote state returning a single Alive storage attachment. 167 var calls int 168 w := newMockNotifyWatcher() 169 st := &mockStorageAccessor{ 170 watchStorageAttachment: func(s names.StorageTag, u names.UnitTag) (watcher.NotifyWatcher, error) { 171 return w, nil 172 }, 173 storageAttachment: func(s names.StorageTag, u names.UnitTag) (params.StorageAttachment, error) { 174 calls++ 175 switch calls { 176 case 1: 177 return params.StorageAttachment{}, ¶ms.Error{Code: params.CodeNotFound} 178 case 2: 179 return params.StorageAttachment{}, ¶ms.Error{Code: params.CodeNotProvisioned} 180 case 3: 181 // This error should cause the source to stop with an error. 182 return params.StorageAttachment{}, ¶ms.Error{ 183 Code: params.CodeUnauthorized, 184 Message: "unauthorized", 185 } 186 } 187 panic("unexpected call to StorageAttachment") 188 }, 189 } 190 191 const initiallyUnattached = false 192 source, err := storage.NewStorageSource(st, unitTag, storageTag, initiallyUnattached) 193 c.Assert(err, jc.ErrorIsNil) 194 195 assertNoSourceChange := func() { 196 select { 197 case <-source.Changes(): 198 c.Fatal("unexpected source change") 199 case <-time.After(testing.ShortWait): 200 } 201 } 202 waitSourceChange := func() hook.SourceChange { 203 select { 204 case ch, ok := <-source.Changes(): 205 c.Assert(ok, jc.IsTrue) 206 assertNoSourceChange() 207 return ch 208 case <-time.After(testing.LongWait): 209 c.Fatal("timed out waiting for source change") 210 panic("unreachable") 211 } 212 } 213 214 assertNoSourceChange() 215 216 // First change is "NotFound": not an error. 217 w.changes <- struct{}{} 218 change := waitSourceChange() 219 c.Assert(change(), jc.ErrorIsNil) 220 221 // Second change is "NotProvisioned": not an error. 222 w.changes <- struct{}{} 223 change = waitSourceChange() 224 c.Assert(change(), jc.ErrorIsNil) 225 226 // Third change is "Unauthorized": this *is* an error. 227 w.changes <- struct{}{} 228 change = waitSourceChange() 229 c.Assert(change(), gc.ErrorMatches, "refreshing storage details: unauthorized") 230 }