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  }