github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/azure/storage.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azure
     5  
     6  import (
     7  	"io"
     8  	"net/http"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils"
    14  	"launchpad.net/gwacl"
    15  
    16  	"github.com/juju/juju/environs/storage"
    17  )
    18  
    19  type azureStorage struct {
    20  	mutex            sync.Mutex
    21  	createdContainer bool
    22  	storageContext
    23  }
    24  
    25  // storageContext is an abstraction that is there only to accommodate the need
    26  // for using an azureStorage independently from an environ object in tests.
    27  type storageContext interface {
    28  	getContainer() string
    29  	getStorageContext() (*gwacl.StorageContext, error)
    30  }
    31  
    32  // environStorageContext is a storageContext which gets its information from
    33  // an azureEnviron object.
    34  type environStorageContext struct {
    35  	environ *azureEnviron
    36  }
    37  
    38  var _ storageContext = (*environStorageContext)(nil)
    39  
    40  func (context *environStorageContext) getContainer() string {
    41  	return context.environ.getContainerName()
    42  }
    43  
    44  func (context *environStorageContext) getStorageContext() (*gwacl.StorageContext, error) {
    45  	return context.environ.getStorageContext()
    46  }
    47  
    48  // azureStorage implements Storage.
    49  var _ storage.Storage = (*azureStorage)(nil)
    50  
    51  // Get is specified in the StorageReader interface.
    52  func (storage *azureStorage) Get(name string) (io.ReadCloser, error) {
    53  	context, err := storage.getStorageContext()
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	reader, err := context.GetBlob(storage.getContainer(), name)
    58  	if gwacl.IsNotFoundError(err) {
    59  		return nil, errors.NotFoundf("file %q not found", name)
    60  	}
    61  	return reader, err
    62  }
    63  
    64  // List is specified in the StorageReader interface.
    65  func (storage *azureStorage) List(prefix string) ([]string, error) {
    66  	context, err := storage.getStorageContext()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	request := &gwacl.ListBlobsRequest{Container: storage.getContainer(), Prefix: prefix, Marker: ""}
    71  	blobList, err := context.ListAllBlobs(request)
    72  	httpErr, isHTTPErr := err.(gwacl.HTTPError)
    73  	if isHTTPErr && httpErr.StatusCode() == http.StatusNotFound {
    74  		// A 404 means the container doesn't exist.  There are no files so
    75  		// just return nothing.
    76  		return nil, nil
    77  	}
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	names := make([]string, len(blobList.Blobs))
    82  	for index, blob := range blobList.Blobs {
    83  		names[index] = blob.Name
    84  	}
    85  	return names, nil
    86  }
    87  
    88  // URL is specified in the StorageReader interface.
    89  func (storage *azureStorage) URL(name string) (string, error) {
    90  	context, err := storage.getStorageContext()
    91  	if err != nil {
    92  		return "", err
    93  	}
    94  	if context.Key != "" {
    95  		// 10 years should be good enough.
    96  		expires := time.Now().AddDate(10, 0, 0)
    97  		return context.GetAnonymousFileURL(storage.getContainer(), name, expires)
    98  	}
    99  	return context.GetFileURL(storage.getContainer(), name), nil
   100  }
   101  
   102  // DefaultConsistencyStrategy is specified in the StorageReader interface.
   103  func (storage *azureStorage) DefaultConsistencyStrategy() utils.AttemptStrategy {
   104  	// This storage backend has immediate consistency, so there's no
   105  	// need to wait.  One attempt should do.
   106  	return utils.AttemptStrategy{}
   107  }
   108  
   109  // ShouldRetry is specified in the StorageReader interface.
   110  func (storage *azureStorage) ShouldRetry(err error) bool {
   111  	return false
   112  }
   113  
   114  // Put is specified in the StorageWriter interface.
   115  func (storage *azureStorage) Put(name string, r io.Reader, length int64) error {
   116  	err := storage.createContainer(storage.getContainer())
   117  	if err != nil {
   118  		return err
   119  	}
   120  	limitedReader := io.LimitReader(r, length)
   121  	context, err := storage.getStorageContext()
   122  	if err != nil {
   123  		return err
   124  	}
   125  	return context.UploadBlockBlob(storage.getContainer(), name, limitedReader)
   126  }
   127  
   128  // Remove is specified in the StorageWriter interface.
   129  func (storage *azureStorage) Remove(name string) error {
   130  	context, err := storage.getStorageContext()
   131  	if err != nil {
   132  		return err
   133  	}
   134  	return context.DeleteBlob(storage.getContainer(), name)
   135  }
   136  
   137  // RemoveAll is specified in the StorageWriter interface.
   138  func (storage *azureStorage) RemoveAll() error {
   139  	context, err := storage.getStorageContext()
   140  	if err != nil {
   141  		return err
   142  	}
   143  	return context.DeleteContainer(storage.getContainer())
   144  }
   145  
   146  // createContainer makes a private container in the storage account.
   147  // It can be called when the container already exists and returns with no error
   148  // if it does.  To avoid unnecessary HTTP requests, we do this only once for
   149  // every PUT operation by using a mutex lock and boolean flag.
   150  func (storage *azureStorage) createContainer(name string) error {
   151  	// We must get our storage context before entering our critical
   152  	// section, because this may lock the environment.
   153  	context, err := storage.getStorageContext()
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	storage.mutex.Lock()
   159  	defer storage.mutex.Unlock()
   160  
   161  	if storage.createdContainer {
   162  		return nil
   163  	}
   164  	_, err = context.GetContainerProperties(name)
   165  	if err == nil {
   166  		// No error means it's already there, just return now.
   167  		return nil
   168  	}
   169  	httpErr, isHTTPErr := err.(gwacl.HTTPError)
   170  	if !isHTTPErr || httpErr.StatusCode() != http.StatusNotFound {
   171  		// We were hoping for a 404: Not Found.  That means we can go
   172  		// ahead and create the container.  But we got some other
   173  		// error.
   174  		return err
   175  	}
   176  	err = context.CreateContainer(name)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	storage.createdContainer = true
   181  	return nil
   182  }
   183  
   184  // deleteContainer deletes the named comtainer from the storage account.
   185  func (storage *azureStorage) deleteContainer(name string) error {
   186  	context, err := storage.getStorageContext()
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	return context.DeleteContainer(name)
   192  }