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  }