github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/storage/state.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 "io/ioutil" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/juju/errors" 13 "github.com/juju/utils" 14 "gopkg.in/juju/charm.v6/hooks" 15 "gopkg.in/juju/names.v2" 16 17 "github.com/juju/juju/worker/uniter/hook" 18 ) 19 20 // state describes the state of a storage attachment. 21 type state struct { 22 // storage is the tag of the storage attachment. 23 storage names.StorageTag 24 25 // attached records the uniter's knowledge of the 26 // storage attachment state. 27 attached bool 28 } 29 30 // ValidateHook returns an error if the supplied hook.Info does not represent 31 // a valid change to the storage state. Hooks must always be validated 32 // against the current state before they are run, to ensure that the system 33 // meets its guarantees about hook execution order. 34 func (s *state) ValidateHook(hi hook.Info) (err error) { 35 defer errors.DeferredAnnotatef(&err, "inappropriate %q hook for storage %q", hi.Kind, s.storage.Id()) 36 if hi.StorageId != s.storage.Id() { 37 return errors.Errorf("expected storage %q, got storage %q", s.storage.Id(), hi.StorageId) 38 } 39 switch hi.Kind { 40 case hooks.StorageAttached: 41 if s.attached { 42 return errors.New("storage already attached") 43 } 44 case hooks.StorageDetaching: 45 if !s.attached { 46 return errors.New("storage not attached") 47 } 48 } 49 return nil 50 } 51 52 // stateFile is a filesystem-backed representation of the state of a 53 // storage attachment. Concurrent modifications to the underlying state 54 // file will have undefined consequences. 55 type stateFile struct { 56 // path identifies the directory holding persistent state. 57 path string 58 59 // state is the cached state of the directory, which is guaranteed 60 // to be synchronized with the true state so long as no concurrent 61 // changes are made to the directory. 62 state 63 } 64 65 // readStateFile loads a stateFile from the subdirectory of dirPath named 66 // for the supplied storage tag. If the directory does not exist, no error 67 // is returned. 68 func readStateFile(dirPath string, tag names.StorageTag) (d *stateFile, err error) { 69 filename := strings.Replace(tag.Id(), "/", "-", -1) 70 d = &stateFile{ 71 filepath.Join(dirPath, filename), 72 state{storage: tag}, 73 } 74 defer errors.DeferredAnnotatef(&err, "cannot load storage %q state from %q", tag.Id(), d.path) 75 if _, err := os.Stat(d.path); os.IsNotExist(err) { 76 return d, nil 77 } else if err != nil { 78 return nil, err 79 } 80 var info diskInfo 81 if err := utils.ReadYaml(d.path, &info); err != nil { 82 return nil, errors.Errorf("invalid storage state file %q: %v", d.path, err) 83 } 84 if info.Attached == nil { 85 return nil, errors.Errorf("invalid storage state file %q: missing 'attached'", d.path) 86 } 87 d.state.attached = *info.Attached 88 return d, nil 89 } 90 91 // readAllStateFiles loads and returns every stateFile persisted inside 92 // the supplied dirPath. If dirPath does not exist, no error is returned. 93 func readAllStateFiles(dirPath string) (files map[names.StorageTag]*stateFile, err error) { 94 defer errors.DeferredAnnotatef(&err, "cannot load storage state from %q", dirPath) 95 if _, err := os.Stat(dirPath); os.IsNotExist(err) { 96 return nil, nil 97 } else if err != nil { 98 return nil, err 99 } 100 fis, err := ioutil.ReadDir(dirPath) 101 if err != nil { 102 return nil, err 103 } 104 files = make(map[names.StorageTag]*stateFile) 105 for _, fi := range fis { 106 if fi.IsDir() { 107 continue 108 } 109 storageId := fi.Name() 110 if i := strings.LastIndex(storageId, "-"); i > 0 { 111 storageId = storageId[:i] + "/" + storageId[i+1:] 112 if !names.IsValidStorage(storageId) { 113 continue 114 } 115 } else { 116 // Lack of "-" means it's not a valid storage ID. 117 continue 118 } 119 tag := names.NewStorageTag(storageId) 120 f, err := readStateFile(dirPath, tag) 121 if err != nil { 122 return nil, err 123 } 124 files[tag] = f 125 } 126 return files, nil 127 } 128 129 // CommitHook atomically writes to disk the storage state change in hi. 130 // It must be called after the respective hook was executed successfully. 131 // CommitHook doesn't validate hi but guarantees that successive writes 132 // of the same hi are idempotent. 133 func (d *stateFile) CommitHook(hi hook.Info) (err error) { 134 defer errors.DeferredAnnotatef(&err, "failed to write %q hook info for %q on state directory", hi.Kind, hi.StorageId) 135 if hi.Kind == hooks.StorageDetaching { 136 return d.Remove() 137 } 138 attached := true 139 di := diskInfo{&attached} 140 if err := utils.WriteYaml(d.path, &di); err != nil { 141 return err 142 } 143 // If write was successful, update own state. 144 d.state.attached = true 145 return nil 146 } 147 148 // Remove removes the directory if it exists and is empty. 149 func (d *stateFile) Remove() error { 150 if err := os.Remove(d.path); err != nil && !os.IsNotExist(err) { 151 return err 152 } 153 // If atomic delete succeeded, update own state. 154 d.state.attached = false 155 return nil 156 } 157 158 // diskInfo defines the storage attachment data serialization. 159 type diskInfo struct { 160 Attached *bool `yaml:"attached,omitempty"` 161 }