github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/joyent/storage.go (about) 1 // Copyright 2013 Joyent Inc. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package joyent 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "path" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/joyent/gocommon/client" 17 je "github.com/joyent/gocommon/errors" 18 "github.com/joyent/gomanta/manta" 19 "github.com/juju/errors" 20 "github.com/juju/utils" 21 22 "github.com/juju/juju/environs/storage" 23 ) 24 25 type JoyentStorage struct { 26 sync.Mutex 27 ecfg *environConfig 28 madeContainer bool 29 containerName string 30 manta *manta.Client 31 } 32 33 type byteCloser struct { 34 io.Reader 35 } 36 37 func (byteCloser) Close() error { 38 return nil 39 } 40 41 var _ storage.Storage = (*JoyentStorage)(nil) 42 43 func newStorage(cfg *environConfig, name string) (storage.Storage, error) { 44 creds, err := credentials(cfg) 45 if err != nil { 46 return nil, err 47 } 48 client := client.NewClient(cfg.mantaUrl(), "", creds, &logger) 49 50 if name == "" { 51 name = cfg.controlDir() 52 } 53 54 return &JoyentStorage{ 55 ecfg: cfg, 56 containerName: name, 57 manta: manta.New(client)}, nil 58 } 59 60 func (s *JoyentStorage) GetContainerName() string { 61 return s.containerName 62 } 63 64 func (s *JoyentStorage) GetMantaUrl() string { 65 return s.ecfg.mantaUrl() 66 } 67 68 func (s *JoyentStorage) GetMantaUser() string { 69 return s.ecfg.mantaUser() 70 } 71 72 // createContainer makes the environment's control container, the 73 // place where bootstrap information and deployed charms 74 // are stored. To avoid two round trips on every PUT operation, 75 // we do this only once for each environ. 76 func (s *JoyentStorage) createContainer() error { 77 s.Lock() 78 defer s.Unlock() 79 if s.madeContainer { 80 return nil 81 } 82 // try to make the container 83 err := s.manta.PutDirectory(s.containerName) 84 if err == nil { 85 s.madeContainer = true 86 } 87 return err 88 } 89 90 // DeleteContainer deletes the named container from the storage account. 91 func (s *JoyentStorage) DeleteContainer(containerName string) error { 92 err := s.manta.DeleteDirectory(containerName) 93 if err == nil && strings.EqualFold(s.containerName, containerName) { 94 s.madeContainer = false 95 } 96 if je.IsResourceNotFound(err) { 97 return errors.NewNotFound(err, fmt.Sprintf("cannot delete %s, not found", containerName)) 98 } 99 return err 100 } 101 102 func (s *JoyentStorage) List(prefix string) ([]string, error) { 103 content, err := list(s, s.containerName) 104 if err != nil { 105 return nil, err 106 } 107 108 var names []string 109 for _, item := range content { 110 name := strings.TrimPrefix(item, s.containerName+"/") 111 if prefix != "" { 112 if strings.HasPrefix(name, prefix) { 113 names = append(names, name) 114 } 115 } else { 116 names = append(names, name) 117 } 118 } 119 return names, nil 120 } 121 122 func list(s *JoyentStorage, path string) ([]string, error) { 123 // TODO - we don't want to create the container here, but instead handle 124 // any 404 and return as if no files exist. 125 if err := s.createContainer(); err != nil { 126 return nil, fmt.Errorf("cannot make Manta control container: %v", err) 127 } 128 // use empty opts, i.e. default values 129 // -- might be added in the provider config? 130 contents, err := s.manta.ListDirectory(path, manta.ListDirectoryOpts{}) 131 if err != nil { 132 return nil, err 133 } 134 var names []string 135 for _, item := range contents { 136 if strings.EqualFold(item.Type, "directory") { 137 items, err := list(s, path+"/"+item.Name) 138 if err != nil { 139 return nil, err 140 } 141 names = append(names, items...) 142 } else { 143 names = append(names, path+"/"+item.Name) 144 } 145 } 146 return names, nil 147 } 148 149 //return something that a random wget can retrieve the object at, without any credentials 150 func (s *JoyentStorage) URL(name string) (string, error) { 151 path := fmt.Sprintf("/%s/stor/%s/%s", s.ecfg.mantaUser(), s.containerName, name) 152 return s.manta.SignURL(path, time.Now().AddDate(10, 0, 0)) 153 } 154 155 func (s *JoyentStorage) Get(name string) (io.ReadCloser, error) { 156 b, err := s.manta.GetObject(s.containerName, name) 157 if err != nil { 158 return nil, errors.NewNotFound(err, fmt.Sprintf("cannot find %s", name)) 159 } 160 r := byteCloser{bytes.NewReader(b)} 161 return r, nil 162 } 163 164 func (s *JoyentStorage) Put(name string, r io.Reader, length int64) error { 165 if err := s.createContainer(); err != nil { 166 return fmt.Errorf("cannot make Manta control container: %v", err) 167 } 168 if strings.Contains(name, "/") { 169 var parents []string 170 dirs := strings.Split(name, "/") 171 for i := range dirs { 172 if i < (len(dirs) - 1) { 173 parents = append(parents, strings.Join(dirs[:(i+1)], "/")) 174 } 175 } 176 for _, dir := range parents { 177 err := s.manta.PutDirectory(path.Join(s.containerName, dir)) 178 if err != nil { 179 return fmt.Errorf("cannot create parent directory %q in control container %q: %v", dir, s.containerName, err) 180 } 181 } 182 } 183 object, err := ioutil.ReadAll(r) 184 if err != nil { 185 return fmt.Errorf("failed to read object %q: %v", name, err) 186 } 187 err = s.manta.PutObject(s.containerName, name, object) 188 if err != nil { 189 return fmt.Errorf("cannot write file %q to control container %q: %v", name, s.containerName, err) 190 } 191 return nil 192 } 193 194 func (s *JoyentStorage) Remove(name string) error { 195 err := s.manta.DeleteObject(s.containerName, name) 196 if err != nil { 197 if je.IsResourceNotFound(err) { 198 // gojoyent returns an error if file doesn't exist 199 // just log a warning 200 logger.Warningf("cannot delete %s from %s, already deleted", name, s.containerName) 201 } else { 202 return err 203 } 204 } 205 206 if strings.Contains(name, "/") { 207 var parents []string 208 dirs := strings.Split(name, "/") 209 for i := (len(dirs) - 1); i >= 0; i-- { 210 if i < (len(dirs) - 1) { 211 parents = append(parents, strings.Join(dirs[:(i+1)], "/")) 212 } 213 } 214 215 for _, dir := range parents { 216 err := s.manta.DeleteDirectory(path.Join(s.containerName, dir)) 217 if err != nil { 218 if je.IsBadRequest(err) { 219 // check if delete request returned a bad request error, i.e. directory is not empty 220 // just log a warning 221 logger.Warningf("cannot delete %s, not empty", dir) 222 } else if je.IsResourceNotFound(err) { 223 // check if delete request returned a resource not found error, i.e. directory was already deleted 224 // just log a warning 225 logger.Warningf("cannot delete %s, already deleted", dir) 226 } else { 227 return fmt.Errorf("cannot delete parent directory %q in control container %q: %v", dir, s.containerName, err) 228 } 229 } 230 } 231 } 232 233 return nil 234 } 235 236 func (s *JoyentStorage) RemoveAll() error { 237 names, err := storage.List(s, "") 238 if err != nil { 239 return err 240 } 241 // Remove all the objects in parallel so that we incur less round-trips. 242 // If we're in danger of having hundreds of objects, 243 // we'll want to change this to limit the number 244 // of concurrent operations. 245 var wg sync.WaitGroup 246 wg.Add(len(names)) 247 errc := make(chan error, len(names)) 248 for _, name := range names { 249 name := name 250 go func() { 251 defer wg.Done() 252 if err := s.Remove(name); err != nil { 253 errc <- err 254 } 255 }() 256 } 257 wg.Wait() 258 select { 259 case err := <-errc: 260 return fmt.Errorf("cannot delete all provider state: %v", err) 261 default: 262 } 263 264 s.Lock() 265 defer s.Unlock() 266 // Even DeleteContainer fails, it won't harm if we try again - the 267 // operation might have succeeded even if we get an error. 268 s.madeContainer = false 269 if err = s.manta.DeleteDirectory(s.containerName); err != nil { 270 return err 271 } 272 return nil 273 } 274 275 func (s *JoyentStorage) DefaultConsistencyStrategy() utils.AttemptStrategy { 276 return utils.AttemptStrategy{} 277 } 278 279 func (s *JoyentStorage) ShouldRetry(err error) bool { 280 return false 281 }