github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 }