github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/storage/attachments.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package storage contains the storage subsystem for the uniter, responding 5 // to changes in storage attachments (lifecycle, volume/filesystem details) 6 // by queuing hooks and managing the storage attachments' lifecycle. 7 package storage 8 9 import ( 10 "os" 11 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 "github.com/juju/names" 15 "github.com/juju/utils/set" 16 "gopkg.in/juju/charm.v5/hooks" 17 18 "github.com/juju/juju/api/watcher" 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/worker/uniter/hook" 21 "github.com/juju/juju/worker/uniter/runner/jujuc" 22 ) 23 24 var logger = loggo.GetLogger("juju.worker.uniter.storage") 25 26 // StorageAccessor is an interface for accessing information about 27 // storage attachments. 28 type StorageAccessor interface { 29 // WatchStorageAttachment starts a watcher for changes to the 30 // storage attachment with the specified unit and storage tags. 31 WatchStorageAttachment(names.StorageTag, names.UnitTag) (watcher.NotifyWatcher, error) 32 33 // StorageAttachment returns details of the storage attachment 34 // with the specified unit and storage tags. 35 StorageAttachment(names.StorageTag, names.UnitTag) (params.StorageAttachment, error) 36 37 // StorageAttachmentLife returns the lifecycle state of the specified 38 // storage attachments. 39 StorageAttachmentLife([]params.StorageAttachmentId) ([]params.LifeResult, error) 40 41 // UnitStorageAttachments returns details of all of the storage 42 // attachments for the unit with the specified tag. 43 UnitStorageAttachments(names.UnitTag) ([]params.StorageAttachmentId, error) 44 45 // DestroyUnitStorageAttachments ensures that all storage 46 // attachments for the specified unit will be removed at 47 // some point in the future. 48 DestroyUnitStorageAttachments(names.UnitTag) error 49 50 // RemoveStorageAttachment removes that the storage attachment 51 // with the specified unit and storage tags. This method is only 52 // expected to succeed if the storage attachment is Dead. 53 RemoveStorageAttachment(names.StorageTag, names.UnitTag) error 54 } 55 56 // Attachments generates storage hooks in response to changes to 57 // storage attachments, and provides access to information about 58 // storage attachments to hooks. 59 type Attachments struct { 60 st StorageAccessor 61 unitTag names.UnitTag 62 abort <-chan struct{} 63 hooks chan hook.Info 64 storagers map[names.StorageTag]*storager 65 storageStateDir string 66 67 // pending is the set of tags for storage attachments 68 // for which no hooks have been run. 69 pending set.Tags 70 } 71 72 // NewAttachments returns a new Attachments. 73 func NewAttachments( 74 st StorageAccessor, 75 tag names.UnitTag, 76 storageStateDir string, 77 abort <-chan struct{}, 78 ) (*Attachments, error) { 79 a := &Attachments{ 80 st: st, 81 unitTag: tag, 82 abort: abort, 83 hooks: make(chan hook.Info), 84 storagers: make(map[names.StorageTag]*storager), 85 storageStateDir: storageStateDir, 86 pending: make(set.Tags), 87 } 88 if err := a.init(); err != nil { 89 return nil, err 90 } 91 return a, nil 92 } 93 94 // init processes the storage state directory and creates storagers 95 // for the state files found. 96 func (a *Attachments) init() error { 97 if err := os.MkdirAll(a.storageStateDir, 0755); err != nil { 98 return errors.Annotate(err, "creating storage state dir") 99 } 100 // Query all remote, known storage attachments for the unit, 101 // so we can cull state files, and store current context. 102 attachmentIds, err := a.st.UnitStorageAttachments(a.unitTag) 103 if err != nil { 104 return errors.Annotate(err, "getting unit attachments") 105 } 106 attachmentsByTag := make(map[names.StorageTag]struct{}) 107 for _, attachmentId := range attachmentIds { 108 storageTag, err := names.ParseStorageTag(attachmentId.StorageTag) 109 if err != nil { 110 return errors.Trace(err) 111 } 112 attachmentsByTag[storageTag] = struct{}{} 113 } 114 stateFiles, err := readAllStateFiles(a.storageStateDir) 115 if err != nil { 116 return errors.Annotate(err, "reading storage state dirs") 117 } 118 for storageTag, stateFile := range stateFiles { 119 if _, ok := attachmentsByTag[storageTag]; !ok { 120 if err := stateFile.Remove(); err != nil { 121 return errors.Trace(err) 122 } 123 continue 124 } 125 // Since there's a state file, we must previously have handled 126 // at least "storage-attached", so there is no possibility of 127 // short-circuiting the storage's removal. 128 if err := a.add(storageTag, stateFile); err != nil { 129 return errors.Trace(err) 130 } 131 } 132 for storageTag := range attachmentsByTag { 133 if _, ok := stateFiles[storageTag]; !ok { 134 // There is no state file for the attachment, so no 135 // hooks have been committed for it. 136 a.pending.Add(storageTag) 137 } 138 // Non-locally recorded attachments will be further handled 139 // by UpdateStorage. 140 } 141 return nil 142 } 143 144 // Hooks returns the channel on which storage hook execution requests 145 // are sent. 146 func (a *Attachments) Hooks() <-chan hook.Info { 147 return a.hooks 148 } 149 150 // Stop stops all of the storagers. 151 func (a *Attachments) Stop() error { 152 for _, s := range a.storagers { 153 if err := s.Stop(); err != nil { 154 return errors.Trace(err) 155 } 156 } 157 return nil 158 } 159 160 // SetDying ensures that any unprovisioned storage attachments are removed 161 // from state, and Pending is updated. After SetDying returns successfully, 162 // and once Pending returns zero and Empty returns true, there will be no 163 // remaining storage attachments. 164 func (a *Attachments) SetDying() error { 165 if err := a.st.DestroyUnitStorageAttachments(a.unitTag); err != nil { 166 return errors.Trace(err) 167 } 168 if err := a.Refresh(); err != nil { 169 return errors.Trace(err) 170 } 171 return nil 172 } 173 174 // Refresh fetches all of the unit's storage attachments and processes each 175 // one as in UpdateStorage. 176 func (a *Attachments) Refresh() error { 177 attachmentIds, err := a.st.UnitStorageAttachments(a.unitTag) 178 if err != nil { 179 return errors.Annotate(err, "getting unit attachments") 180 } 181 storageTags := make([]names.StorageTag, len(attachmentIds)) 182 for i, attachmentId := range attachmentIds { 183 storageTag, err := names.ParseStorageTag(attachmentId.StorageTag) 184 if err != nil { 185 return errors.Trace(err) 186 } 187 storageTags[i] = storageTag 188 } 189 // Remove non-existent storage from pending. 190 for pending := range a.pending { 191 var found bool 192 for _, active := range storageTags { 193 if pending == active { 194 found = true 195 break 196 } 197 } 198 if !found { 199 a.pending.Remove(pending) 200 } 201 } 202 return a.UpdateStorage(storageTags) 203 } 204 205 // UpdateStorage responds to changes in the lifecycle states of the 206 // storage attachments corresponding to the supplied storage tags, 207 // sending storage hooks on the channel returned by Hooks(). 208 func (a *Attachments) UpdateStorage(tags []names.StorageTag) error { 209 ids := make([]params.StorageAttachmentId, len(tags)) 210 for i, storageTag := range tags { 211 ids[i] = params.StorageAttachmentId{ 212 StorageTag: storageTag.String(), 213 UnitTag: a.unitTag.String(), 214 } 215 } 216 results, err := a.st.StorageAttachmentLife(ids) 217 if err != nil { 218 return errors.Trace(err) 219 } 220 for i, result := range results { 221 if result.Error == nil { 222 continue 223 } else if params.IsCodeNotFound(result.Error) { 224 a.pending.Remove(tags[i]) 225 continue 226 } 227 return errors.Annotatef( 228 result.Error, "getting life of storage %s attachment", tags[i].Id(), 229 ) 230 } 231 for i, result := range results { 232 if result.Error != nil { 233 continue 234 } 235 if err := a.updateOneStorage(tags[i], result.Life); err != nil { 236 return errors.Trace(err) 237 } 238 } 239 return nil 240 } 241 242 func (a *Attachments) updateOneStorage(storageTag names.StorageTag, life params.Life) error { 243 // Fetch the attachment's remote state, so we know when we can 244 // stop the storager and possibly short-circuit the attachment's 245 // removal. 246 stateFile, err := readStateFile(a.storageStateDir, storageTag) 247 if err != nil { 248 return errors.Trace(err) 249 } 250 storager := a.storagers[storageTag] 251 252 switch life { 253 case params.Dying: 254 if stateFile.state.attached { 255 // Previously ran storage-attached, so we'll 256 // leave the storager to handle the lifecycle 257 // state change. 258 if storager == nil { 259 panic("missing storager for attached storage") 260 } 261 return nil 262 } 263 // Storage attachment hasn't previously been observed, 264 // so we can short-circuit the removal. 265 err := a.removeStorageAttachment(storageTag, storager) 266 return errors.Trace(err) 267 } 268 269 if storager == nil { 270 a.pending.Add(storageTag) 271 return a.add(storageTag, stateFile) 272 } 273 return nil 274 } 275 276 // add creates a new storager for the specified storage tag. 277 func (a *Attachments) add(storageTag names.StorageTag, stateFile *stateFile) error { 278 s, err := newStorager(a.st, a.unitTag, storageTag, stateFile, a.hooks) 279 if err != nil { 280 return errors.Annotatef(err, "watching storage %q", storageTag.Id()) 281 } 282 a.storagers[storageTag] = s 283 logger.Debugf("watching storage %q", storageTag.Id()) 284 return nil 285 } 286 287 // Pending reports the number of storage attachments whose hooks have yet 288 // to be run and committed. 289 func (a *Attachments) Pending() int { 290 return a.pending.Size() 291 } 292 293 // Empty reports whether or not there are any active storage attachments. 294 func (a *Attachments) Empty() bool { 295 return len(a.storagers) == 0 296 } 297 298 // Storage returns the ContextStorage with the supplied tag if it was 299 // found, and whether it was found. 300 func (a *Attachments) Storage(tag names.StorageTag) (jujuc.ContextStorageAttachment, bool) { 301 if s, ok := a.storagers[tag]; ok { 302 return s.Context() 303 } 304 return nil, false 305 } 306 307 // StorageTags returns the names.StorageTags for the active storage attachments. 308 func (a *Attachments) StorageTags() []names.StorageTag { 309 tags := set.NewTags() 310 for tag := range a.storagers { 311 tags.Add(tag) 312 } 313 storageTags := make([]names.StorageTag, tags.Size()) 314 for i, tag := range tags.SortedValues() { 315 storageTags[i] = tag.(names.StorageTag) 316 } 317 return storageTags 318 } 319 320 // ValidateHook validates the hook against the current state. 321 func (a *Attachments) ValidateHook(hi hook.Info) error { 322 storager, err := a.storagerForHook(hi) 323 if err != nil { 324 return errors.Trace(err) 325 } 326 return storager.state.ValidateHook(hi) 327 } 328 329 // CommitHook persists the state change encoded in the supplied storage 330 // hook, or returns an error if the hook is invalid given current state. 331 func (a *Attachments) CommitHook(hi hook.Info) error { 332 storager, err := a.storagerForHook(hi) 333 if err != nil { 334 return errors.Trace(err) 335 } 336 if err := storager.CommitHook(hi); err != nil { 337 return err 338 } 339 storageTag := names.NewStorageTag(hi.StorageId) 340 switch hi.Kind { 341 case hooks.StorageAttached: 342 a.pending.Remove(storageTag) 343 case hooks.StorageDetaching: 344 if err := a.removeStorageAttachment(storageTag, storager); err != nil { 345 return errors.Trace(err) 346 } 347 } 348 return nil 349 } 350 351 func (a *Attachments) removeStorageAttachment(tag names.StorageTag, s *storager) error { 352 if err := a.st.RemoveStorageAttachment(tag, a.unitTag); err != nil { 353 return errors.Annotate(err, "removing storage attachment") 354 } 355 a.pending.Remove(tag) 356 if s == nil { 357 return nil 358 } 359 if err := s.Stop(); err != nil { 360 return errors.Trace(err) 361 } 362 delete(a.storagers, tag) 363 return nil 364 } 365 366 func (a *Attachments) storagerForHook(hi hook.Info) (*storager, error) { 367 if !hi.Kind.IsStorage() { 368 return nil, errors.Errorf("not a storage hook: %#v", hi) 369 } 370 storager, ok := a.storagers[names.NewStorageTag(hi.StorageId)] 371 if !ok { 372 return nil, errors.Errorf("unknown storage %q", hi.StorageId) 373 } 374 return storager, nil 375 }