github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/storage/source.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storage 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/names" 9 "gopkg.in/juju/charm.v5/hooks" 10 "launchpad.net/tomb" 11 12 apiwatcher "github.com/juju/juju/api/watcher" 13 "github.com/juju/juju/apiserver/params" 14 "github.com/juju/juju/state/watcher" 15 "github.com/juju/juju/storage" 16 "github.com/juju/juju/worker/uniter/hook" 17 "github.com/juju/juju/worker/uniter/runner/jujuc" 18 ) 19 20 // storageSource is a hook source that generates storage hooks for 21 // a single storage attachment. 22 type storageSource struct { 23 tomb tomb.Tomb 24 25 *storageHookQueue 26 st StorageAccessor 27 watcher apiwatcher.NotifyWatcher 28 changes chan hook.SourceChange 29 } 30 31 // storageHookQueue implements a subset of hook.Source, separated from 32 // storageSource for simpler testing. 33 type storageHookQueue struct { 34 unitTag names.UnitTag 35 storageTag names.StorageTag 36 37 // attached records whether or not the storage-attached 38 // hook has been executed. 39 attached bool 40 41 // hookInfo is the next hook.Info to return, if non-nil. 42 hookInfo *hook.Info 43 44 // context contains the details of the storage attachment. 45 context *contextStorage 46 } 47 48 // newStorageSource creates a hook source that watches for changes to, 49 // and generates storage hooks for, a single storage attachment. 50 func newStorageSource( 51 st StorageAccessor, 52 unitTag names.UnitTag, 53 storageTag names.StorageTag, 54 attached bool, 55 ) (*storageSource, error) { 56 w, err := st.WatchStorageAttachment(storageTag, unitTag) 57 if err != nil { 58 return nil, errors.Annotate(err, "watching storage attachment") 59 } 60 s := &storageSource{ 61 storageHookQueue: &storageHookQueue{ 62 unitTag: unitTag, 63 storageTag: storageTag, 64 attached: attached, 65 }, 66 st: st, 67 watcher: w, 68 changes: make(chan hook.SourceChange), 69 } 70 go func() { 71 defer s.tomb.Done() 72 defer watcher.Stop(w, &s.tomb) 73 s.tomb.Kill(s.loop()) 74 }() 75 return s, nil 76 } 77 78 func (s *storageSource) loop() error { 79 defer close(s.changes) 80 81 var inChanges <-chan struct{} 82 var outChanges chan<- hook.SourceChange 83 var outChange hook.SourceChange 84 ready := make(chan struct{}, 1) 85 ready <- struct{}{} 86 for { 87 select { 88 case <-s.tomb.Dying(): 89 return tomb.ErrDying 90 case <-ready: 91 inChanges = s.watcher.Changes() 92 case _, ok := <-inChanges: 93 logger.Debugf("got storage attachment change") 94 if !ok { 95 return watcher.EnsureErr(s.watcher) 96 } 97 inChanges = nil 98 outChanges = s.changes 99 outChange = func() error { 100 defer func() { 101 ready <- struct{}{} 102 }() 103 logger.Debugf("processing storage source change") 104 return s.update() 105 } 106 case outChanges <- outChange: 107 logger.Debugf("sent storage source change") 108 outChanges = nil 109 outChange = nil 110 } 111 } 112 } 113 114 // Changes is part of the hook.Source interface. 115 func (s *storageSource) Changes() <-chan hook.SourceChange { 116 return s.changes 117 } 118 119 // Stop is part of the hook.Source interface. 120 func (s *storageSource) Stop() error { 121 s.tomb.Kill(nil) 122 return s.tomb.Wait() 123 } 124 125 // update is called when hook.SourceChanges are applied. 126 func (s *storageSource) update() error { 127 attachment, err := s.st.StorageAttachment(s.storageTag, s.unitTag) 128 if params.IsCodeNotFound(err) { 129 // The storage attachment was removed from state, which 130 // implies that the storage has been detached already. 131 logger.Debugf("storage attachment %q not found", s.storageTag.Id()) 132 return nil 133 } else if params.IsCodeNotProvisioned(err) { 134 logger.Debugf("storage attachment %q not provisioned yet", s.storageTag.Id()) 135 return nil 136 } else if err != nil { 137 logger.Debugf("error refreshing storage details: %v", err) 138 return errors.Annotate(err, "refreshing storage details") 139 } 140 return s.storageHookQueue.Update(attachment) 141 } 142 143 // Empty is part of the hook.Source interface. 144 func (s *storageHookQueue) Empty() bool { 145 return s.hookInfo == nil 146 } 147 148 // Next is part of the hook.Source interface. 149 func (s *storageHookQueue) Next() hook.Info { 150 if s.Empty() { 151 panic("source is empty") 152 } 153 return *s.hookInfo 154 } 155 156 // Pop is part of the hook.Source interface. 157 func (s *storageHookQueue) Pop() { 158 if s.Empty() { 159 panic("source is empty") 160 } 161 if s.hookInfo.Kind == hooks.StorageAttached { 162 s.attached = true 163 } 164 s.hookInfo = nil 165 } 166 167 // Update updates the hook queue with the freshly acquired information about 168 // the storage attachment. 169 func (s *storageHookQueue) Update(attachment params.StorageAttachment) error { 170 switch attachment.Life { 171 case params.Alive: 172 if s.attached { 173 // Storage attachments currently do not change 174 // (apart from lifecycle) after being provisioned. 175 // We don't process unprovisioned storage here, 176 // so there's nothing to do. 177 return nil 178 } 179 case params.Dying: 180 if !s.attached { 181 // Nothing to do: attachment is dying, but 182 // the storage-attached hook has not been 183 // consumed. 184 s.hookInfo = nil 185 return nil 186 } 187 case params.Dead: 188 // Storage must been Dying to become Dead; 189 // no further action is required. 190 return nil 191 } 192 193 // Set the storage context when the first hook is generated 194 // for this storager. Later, when we need to handle changing 195 // storage, we'll need to have a cache in the runner like 196 // we have for relations. 197 if s.context == nil { 198 s.context = &contextStorage{ 199 tag: s.storageTag, 200 kind: storage.StorageKind(attachment.Kind), 201 location: attachment.Location, 202 } 203 } 204 205 if s.hookInfo == nil { 206 s.hookInfo = &hook.Info{ 207 StorageId: s.storageTag.Id(), 208 } 209 } 210 if attachment.Life == params.Alive { 211 s.hookInfo.Kind = hooks.StorageAttached 212 } else { 213 s.hookInfo.Kind = hooks.StorageDetaching 214 } 215 logger.Debugf("queued hook: %v", s.hookInfo) 216 return nil 217 } 218 219 // Context returns the ContextStorage for the storage that this hook queue 220 // corresponds to, and whether there is any context available yet. There 221 // will be context beginning from when the first hook is queued. 222 func (s *storageHookQueue) Context() (jujuc.ContextStorageAttachment, bool) { 223 if s.context != nil { 224 return s.context, true 225 } 226 return nil, false 227 }