github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 // TODO(katco): 2016-08-09: lp:1611427 83 var storageAttempt = utils.AttemptStrategy{ 84 Total: 5 * time.Second, 85 Delay: 200 * time.Millisecond, 86 } 87 88 // DefaultConsistencyStrategy is specified in the StorageReader interface. 89 // 90 // TODO(katco): 2016-08-09: lp:1611427 91 func (s *ec2storage) DefaultConsistencyStrategy() utils.AttemptStrategy { 92 return storageAttempt 93 } 94 95 // ShouldRetry is specified in the StorageReader interface. 96 func (s *ec2storage) ShouldRetry(err error) bool { 97 if err == nil { 98 return false 99 } 100 switch err { 101 case io.ErrUnexpectedEOF, io.EOF: 102 return true 103 } 104 if s3ErrorStatusCode(err) == 404 { 105 return true 106 } 107 switch e := err.(type) { 108 case *net.DNSError: 109 return true 110 case *net.OpError: 111 switch e.Op { 112 case "read", "write": 113 return true 114 } 115 case *s3.Error: 116 switch e.Code { 117 case "InternalError": 118 return true 119 } 120 } 121 return false 122 } 123 124 // s3ErrorStatusCode returns the HTTP status of the S3 request error, 125 // if it is an error from an S3 operation, or 0 if it was not. 126 func s3ErrorStatusCode(err error) int { 127 if err, _ := err.(*s3.Error); err != nil { 128 return err.StatusCode 129 } 130 return 0 131 } 132 133 // s3ErrCode returns the text status code of the S3 error code. 134 func s3ErrCode(err error) string { 135 if err, ok := err.(*s3.Error); ok { 136 return err.Code 137 } 138 return "" 139 } 140 141 func (s *ec2storage) Remove(file string) error { 142 err := s.bucket.Del(file) 143 // If we can't delete the object because the bucket doesn't 144 // exist, then we don't care. 145 if s3ErrorStatusCode(err) == 404 { 146 return nil 147 } 148 return err 149 } 150 151 func (s *ec2storage) List(prefix string) ([]string, error) { 152 // TODO cope with more than 1000 objects in the bucket. 153 resp, err := s.bucket.List(prefix, "", "", 0) 154 if err != nil { 155 // If the bucket is not found, it's not an error 156 // because it's only created when the first 157 // file is put. 158 if s3ErrorStatusCode(err) == 404 { 159 return nil, nil 160 } 161 return nil, err 162 } 163 var names []string 164 for _, key := range resp.Contents { 165 names = append(names, key.Key) 166 } 167 return names, nil 168 } 169 170 func (s *ec2storage) RemoveAll() error { 171 names, err := storage.List(s, "") 172 if err != nil { 173 return err 174 } 175 // Remove all the objects in parallel to minimize round-trips. 176 // If we're in danger of having hundreds of objects, 177 // we'll want to change this to limit the number 178 // of concurrent operations. 179 var wg sync.WaitGroup 180 wg.Add(len(names)) 181 errc := make(chan error, len(names)) 182 for _, name := range names { 183 name := name 184 go func() { 185 if err := s.Remove(name); err != nil { 186 errc <- err 187 } 188 wg.Done() 189 }() 190 } 191 wg.Wait() 192 select { 193 case err := <-errc: 194 return fmt.Errorf("cannot delete all provider state: %v", err) 195 default: 196 } 197 198 s.Lock() 199 defer s.Unlock() 200 // Even DelBucket fails, it won't harm if we try again - the operation 201 // might have succeeded even if we get an error. 202 s.madeBucket = false 203 err = deleteBucket(s) 204 err = s.bucket.DelBucket() 205 if s3ErrorStatusCode(err) == 404 { 206 return nil 207 } 208 return err 209 } 210 211 func deleteBucket(s *ec2storage) (err error) { 212 for a := s.DefaultConsistencyStrategy().Start(); a.Next(); { 213 err = s.bucket.DelBucket() 214 if err == nil || !s.ShouldRetry(err) { 215 break 216 } 217 } 218 return err 219 } 220 221 func maybeNotFound(err error) error { 222 if err != nil && s3ErrorStatusCode(err) == 404 { 223 return errors.NewNotFound(err, "") 224 } 225 return err 226 }