github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/ec2/storage.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package ec2 5 6 import ( 7 "fmt" 8 "io" 9 "net" 10 "sync" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/utils" 15 "gopkg.in/amz.v3/s3" 16 17 "github.com/juju/juju/environs/storage" 18 ) 19 20 func init() { 21 // We will decide when to retry and under what circumstances, not s3. 22 // Sometimes it is expected a file may not exist and we don't want s3 23 // to hold things up by unilaterally deciding to retry for no good reason. 24 s3.RetryAttempts(false) 25 } 26 27 func NewStorage(bucket *s3.Bucket) storage.Storage { 28 return &ec2storage{bucket: bucket} 29 } 30 31 // ec2storage implements storage.Storage on 32 // an ec2.bucket. 33 type ec2storage struct { 34 sync.Mutex 35 madeBucket bool 36 bucket *s3.Bucket 37 } 38 39 // makeBucket makes the environent's control bucket, the 40 // place where bootstrap information and deployed charms 41 // are stored. To avoid two round trips on every PUT operation, 42 // we do this only once for each environ. 43 func (s *ec2storage) makeBucket() error { 44 s.Lock() 45 defer s.Unlock() 46 if s.madeBucket { 47 return nil 48 } 49 // PutBucket always return a 200 if we recreate an existing bucket for the 50 // original s3.amazonaws.com endpoint. For all other endpoints PutBucket 51 // returns 409 with a known subcode. 52 if err := s.bucket.PutBucket(s3.Private); err != nil && s3ErrCode(err) != "BucketAlreadyOwnedByYou" { 53 return err 54 } 55 56 s.madeBucket = true 57 return nil 58 } 59 60 func (s *ec2storage) Put(file string, r io.Reader, length int64) error { 61 if err := s.makeBucket(); err != nil { 62 return fmt.Errorf("cannot make S3 control bucket: %v", err) 63 } 64 err := s.bucket.PutReader(file, r, length, "binary/octet-stream", s3.Private) 65 if err != nil { 66 return fmt.Errorf("cannot write file %q to control bucket: %v", file, err) 67 } 68 return nil 69 } 70 71 func (s *ec2storage) Get(file string) (r io.ReadCloser, err error) { 72 r, err = s.bucket.GetReader(file) 73 return r, maybeNotFound(err) 74 } 75 76 func (s *ec2storage) URL(name string) (string, error) { 77 const sevenDays = 168 * time.Hour 78 const maxExpiratoryPeriod = sevenDays 79 return s.bucket.SignedURL(name, maxExpiratoryPeriod) 80 } 81 82 var storageAttempt = utils.AttemptStrategy{ 83 Total: 5 * time.Second, 84 Delay: 200 * time.Millisecond, 85 } 86 87 // DefaultConsistencyStrategy is specified in the StorageReader interface. 88 func (s *ec2storage) DefaultConsistencyStrategy() utils.AttemptStrategy { 89 return storageAttempt 90 } 91 92 // ShouldRetry is specified in the StorageReader interface. 93 func (s *ec2storage) ShouldRetry(err error) bool { 94 if err == nil { 95 return false 96 } 97 switch err { 98 case io.ErrUnexpectedEOF, io.EOF: 99 return true 100 } 101 if s3ErrorStatusCode(err) == 404 { 102 return true 103 } 104 switch e := err.(type) { 105 case *net.DNSError: 106 return true 107 case *net.OpError: 108 switch e.Op { 109 case "read", "write": 110 return true 111 } 112 case *s3.Error: 113 switch e.Code { 114 case "InternalError": 115 return true 116 } 117 } 118 return false 119 } 120 121 // s3ErrorStatusCode returns the HTTP status of the S3 request error, 122 // if it is an error from an S3 operation, or 0 if it was not. 123 func s3ErrorStatusCode(err error) int { 124 if err, _ := err.(*s3.Error); err != nil { 125 return err.StatusCode 126 } 127 return 0 128 } 129 130 // s3ErrCode returns the text status code of the S3 error code. 131 func s3ErrCode(err error) string { 132 if err, ok := err.(*s3.Error); ok { 133 return err.Code 134 } 135 return "" 136 } 137 138 func (s *ec2storage) Remove(file string) error { 139 err := s.bucket.Del(file) 140 // If we can't delete the object because the bucket doesn't 141 // exist, then we don't care. 142 if s3ErrorStatusCode(err) == 404 { 143 return nil 144 } 145 return err 146 } 147 148 func (s *ec2storage) List(prefix string) ([]string, error) { 149 // TODO cope with more than 1000 objects in the bucket. 150 resp, err := s.bucket.List(prefix, "", "", 0) 151 if err != nil { 152 // If the bucket is not found, it's not an error 153 // because it's only created when the first 154 // file is put. 155 if s3ErrorStatusCode(err) == 404 { 156 return nil, nil 157 } 158 return nil, err 159 } 160 var names []string 161 for _, key := range resp.Contents { 162 names = append(names, key.Key) 163 } 164 return names, nil 165 } 166 167 func (s *ec2storage) RemoveAll() error { 168 names, err := storage.List(s, "") 169 if err != nil { 170 return err 171 } 172 // Remove all the objects in parallel to minimize round-trips. 173 // If we're in danger of having hundreds of objects, 174 // we'll want to change this to limit the number 175 // of concurrent operations. 176 var wg sync.WaitGroup 177 wg.Add(len(names)) 178 errc := make(chan error, len(names)) 179 for _, name := range names { 180 name := name 181 go func() { 182 if err := s.Remove(name); err != nil { 183 errc <- err 184 } 185 wg.Done() 186 }() 187 } 188 wg.Wait() 189 select { 190 case err := <-errc: 191 return fmt.Errorf("cannot delete all provider state: %v", err) 192 default: 193 } 194 195 s.Lock() 196 defer s.Unlock() 197 // Even DelBucket fails, it won't harm if we try again - the operation 198 // might have succeeded even if we get an error. 199 s.madeBucket = false 200 err = deleteBucket(s) 201 err = s.bucket.DelBucket() 202 if s3ErrorStatusCode(err) == 404 { 203 return nil 204 } 205 return err 206 } 207 208 func deleteBucket(s *ec2storage) (err error) { 209 for a := s.DefaultConsistencyStrategy().Start(); a.Next(); { 210 err = s.bucket.DelBucket() 211 if err == nil || !s.ShouldRetry(err) { 212 break 213 } 214 } 215 return err 216 } 217 218 func maybeNotFound(err error) error { 219 if err != nil && s3ErrorStatusCode(err) == 404 { 220 return errors.NewNotFound(err, "") 221 } 222 return err 223 }