github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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.v6-unstable/hooks" 17 18 "github.com/juju/juju/apiserver/params" 19 "github.com/juju/juju/worker/uniter/hook" 20 "github.com/juju/juju/worker/uniter/runner/jujuc" 21 ) 22 23 var logger = loggo.GetLogger("juju.worker.uniter.storage") 24 25 // StorageAccessor is an interface for accessing information about 26 // storage attachments. 27 type StorageAccessor interface { 28 // StorageAttachment returns details of the storage attachment 29 // with the specified unit and storage tags. 30 StorageAttachment(names.StorageTag, names.UnitTag) (params.StorageAttachment, error) 31 32 // StorageAttachmentLife returns the lifecycle state of the specified 33 // storage attachments. 34 StorageAttachmentLife([]params.StorageAttachmentId) ([]params.LifeResult, error) 35 36 // UnitStorageAttachments returns details of all of the storage 37 // attachments for the unit with the specified tag. 38 UnitStorageAttachments(names.UnitTag) ([]params.StorageAttachmentId, error) 39 40 // DestroyUnitStorageAttachments ensures that all storage 41 // attachments for the specified unit will be removed at 42 // some point in the future. 43 DestroyUnitStorageAttachments(names.UnitTag) error 44 45 // RemoveStorageAttachment removes that the storage attachment 46 // with the specified unit and storage tags. This method is only 47 // expected to succeed if the storage attachment is Dead. 48 RemoveStorageAttachment(names.StorageTag, names.UnitTag) error 49 } 50 51 type storageAttachment struct { 52 *stateFile 53 jujuc.ContextStorageAttachment 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 storageStateDir string 64 65 // pending is the set of tags for storage attachments 66 // for which no hooks have been run. 67 pending set.Tags 68 69 // current storage attachments 70 storageAttachments map[names.StorageTag]storageAttachment 71 } 72 73 // NewAttachments returns a new Attachments. 74 func NewAttachments( 75 st StorageAccessor, 76 tag names.UnitTag, 77 storageStateDir string, 78 abort <-chan struct{}, 79 ) (*Attachments, error) { 80 a := &Attachments{ 81 st: st, 82 unitTag: tag, 83 abort: abort, 84 storageAttachments: make(map[names.StorageTag]storageAttachment), 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 // SetDying ensures that any unprovisioned storage attachments are removed 145 // from state, and Pending is updated. After SetDying returns successfully, 146 // and once Pending returns zero and Empty returns true, there will be no 147 // remaining storage attachments. 148 func (a *Attachments) SetDying() error { 149 if err := a.st.DestroyUnitStorageAttachments(a.unitTag); err != nil { 150 return errors.Trace(err) 151 } 152 if err := a.Refresh(); err != nil { 153 return errors.Trace(err) 154 } 155 return nil 156 } 157 158 // Refresh fetches all of the unit's storage attachments and processes each 159 // one as in UpdateStorage. 160 func (a *Attachments) Refresh() error { 161 attachmentIds, err := a.st.UnitStorageAttachments(a.unitTag) 162 if err != nil { 163 return errors.Annotate(err, "getting unit attachments") 164 } 165 storageTags := make([]names.StorageTag, len(attachmentIds)) 166 for i, attachmentId := range attachmentIds { 167 storageTag, err := names.ParseStorageTag(attachmentId.StorageTag) 168 if err != nil { 169 return errors.Trace(err) 170 } 171 storageTags[i] = storageTag 172 } 173 // Remove non-existent storage from pending. 174 for pending := range a.pending { 175 var found bool 176 for _, active := range storageTags { 177 if pending == active { 178 found = true 179 break 180 } 181 } 182 if !found { 183 a.pending.Remove(pending) 184 } 185 } 186 return a.UpdateStorage(storageTags) 187 } 188 189 // UpdateStorage responds to changes in the lifecycle states of the 190 // storage attachments corresponding to the supplied storage tags, 191 // sending storage hooks on the channel returned by Hooks(). 192 func (a *Attachments) UpdateStorage(tags []names.StorageTag) error { 193 ids := make([]params.StorageAttachmentId, len(tags)) 194 for i, storageTag := range tags { 195 ids[i] = params.StorageAttachmentId{ 196 StorageTag: storageTag.String(), 197 UnitTag: a.unitTag.String(), 198 } 199 } 200 results, err := a.st.StorageAttachmentLife(ids) 201 if err != nil { 202 return errors.Trace(err) 203 } 204 for i, result := range results { 205 if result.Error == nil { 206 continue 207 } else if params.IsCodeNotFound(result.Error) { 208 a.pending.Remove(tags[i]) 209 continue 210 } 211 return errors.Annotatef( 212 result.Error, "getting life of storage %s attachment", tags[i].Id(), 213 ) 214 } 215 for i, result := range results { 216 if result.Error != nil { 217 continue 218 } 219 if err := a.updateOneStorage(tags[i], result.Life); err != nil { 220 return errors.Trace(err) 221 } 222 } 223 return nil 224 } 225 226 func (a *Attachments) updateOneStorage(storageTag names.StorageTag, life params.Life) error { 227 // Fetch the attachment's remote state, so we know when we can 228 // stop the storager and possibly short-circuit the attachment's 229 // removal. 230 stateFile, err := readStateFile(a.storageStateDir, storageTag) 231 if err != nil { 232 return errors.Trace(err) 233 } 234 _, storageKnown := a.storageAttachments[storageTag] 235 236 switch life { 237 case params.Dying: 238 if stateFile.state.attached { 239 // Previously ran storage-attached, so we'll 240 // leave the external watcher to handle the lifecycle 241 // state change. 242 if !storageKnown { 243 panic("missing status for attached storage") 244 } 245 return nil 246 } 247 // Storage attachment hasn't previously been observed, 248 // so we can short-circuit the removal. 249 err := a.removeStorageAttachment(storageTag, storageKnown) 250 return errors.Trace(err) 251 } 252 253 if !storageKnown { 254 a.pending.Add(storageTag) 255 return a.add(storageTag, stateFile) 256 } 257 return nil 258 } 259 260 // add creates a new storager for the specified storage tag. 261 func (a *Attachments) add(storageTag names.StorageTag, stateFile *stateFile) error { 262 a.storageAttachments[storageTag] = storageAttachment{ 263 stateFile: stateFile, 264 } 265 logger.Debugf("adding storage %q", storageTag.Id()) 266 return nil 267 } 268 269 // Pending reports the number of storage attachments whose hooks have yet 270 // to be run and committed. 271 func (a *Attachments) Pending() int { 272 return a.pending.Size() 273 } 274 275 // Empty reports whether or not there are any active storage attachments. 276 func (a *Attachments) Empty() bool { 277 return len(a.storageAttachments) == 0 278 } 279 280 // Storage returns the ContextStorage with the supplied tag if it was 281 // found, and whether it was found. 282 func (a *Attachments) Storage(tag names.StorageTag) (jujuc.ContextStorageAttachment, error) { 283 if attachment, ok := a.storageAttachments[tag]; ok { 284 return attachment, nil 285 } 286 return nil, errors.NotFoundf("storage") 287 } 288 289 // StorageTags returns the names.StorageTags for the active storage attachments. 290 func (a *Attachments) StorageTags() ([]names.StorageTag, error) { 291 tags := set.NewTags() 292 for tag := range a.storageAttachments { 293 tags.Add(tag) 294 } 295 storageTags := make([]names.StorageTag, tags.Size()) 296 for i, tag := range tags.SortedValues() { 297 storageTags[i] = tag.(names.StorageTag) 298 } 299 return storageTags, nil 300 } 301 302 // ValidateHook validates the hook against the current state. 303 func (a *Attachments) ValidateHook(hi hook.Info) error { 304 storageState, err := a.storageStateForHook(hi) 305 if err != nil { 306 return errors.Trace(err) 307 } 308 return storageState.ValidateHook(hi) 309 } 310 311 // CommitHook persists the state change encoded in the supplied storage 312 // hook, or returns an error if the hook is invalid given current state. 313 func (a *Attachments) CommitHook(hi hook.Info) error { 314 storageState, err := a.storageStateForHook(hi) 315 if err != nil { 316 return errors.Trace(err) 317 } 318 if err := storageState.CommitHook(hi); err != nil { 319 return err 320 } 321 storageTag := names.NewStorageTag(hi.StorageId) 322 switch hi.Kind { 323 case hooks.StorageAttached: 324 a.pending.Remove(storageTag) 325 case hooks.StorageDetaching: 326 if err := a.removeStorageAttachment(storageTag, storageState != nil); err != nil { 327 return errors.Trace(err) 328 } 329 } 330 return nil 331 } 332 333 func (a *Attachments) removeStorageAttachment(tag names.StorageTag, storageKnown bool) error { 334 if err := a.st.RemoveStorageAttachment(tag, a.unitTag); err != nil { 335 return errors.Annotate(err, "removing storage attachment") 336 } 337 a.pending.Remove(tag) 338 if !storageKnown { 339 return nil 340 } 341 delete(a.storageAttachments, tag) 342 return nil 343 } 344 345 func (a *Attachments) storageStateForHook(hi hook.Info) (*stateFile, error) { 346 if !hi.Kind.IsStorage() { 347 return nil, errors.Errorf("not a storage hook: %#v", hi) 348 } 349 storageAttachment, ok := a.storageAttachments[names.NewStorageTag(hi.StorageId)] 350 if !ok { 351 return nil, errors.Errorf("unknown storage %q", hi.StorageId) 352 } 353 return storageAttachment.stateFile, nil 354 }