github.com/cloudfoundry-attic/ltc@v0.0.0-20151123212628-098adc7919fc/blob_store/s3_blob_store/blob_store_test.go (about) 1 package s3_blob_store_test 2 3 import ( 4 "io/ioutil" 5 "net/http" 6 "strings" 7 "time" 8 9 "github.com/aws/aws-sdk-go/aws/client" 10 "github.com/aws/aws-sdk-go/aws/credentials" 11 "github.com/aws/aws-sdk-go/aws/request" 12 . "github.com/onsi/ginkgo" 13 . "github.com/onsi/gomega" 14 "github.com/onsi/gomega/ghttp" 15 16 "github.com/cloudfoundry-incubator/bbs/models" 17 "github.com/cloudfoundry-incubator/ltc/blob_store/blob" 18 "github.com/cloudfoundry-incubator/ltc/blob_store/s3_blob_store" 19 "github.com/cloudfoundry-incubator/ltc/config" 20 ) 21 22 type nullRetryer struct { 23 client.DefaultRetryer 24 } 25 26 func (n nullRetryer) ShouldRetry(_ *request.Request) bool { 27 return false 28 } 29 30 var _ = Describe("BlobStore", func() { 31 var ( 32 blobStore *s3_blob_store.BlobStore 33 fakeServer *ghttp.Server 34 ) 35 36 BeforeEach(func() { 37 blobTargetInfo := config.S3BlobStoreConfig{ 38 AccessKey: "some-access-key", 39 SecretKey: "some-secret-key", 40 BucketName: "bucket", 41 Region: "some-s3-region", 42 } 43 44 blobStore = s3_blob_store.New(blobTargetInfo) 45 blobStore.S3.Retryer = nullRetryer{} 46 47 fakeServer = ghttp.NewServer() 48 blobStore.S3.Endpoint = fakeServer.URL() 49 }) 50 51 AfterEach(func() { 52 fakeServer.Close() 53 }) 54 55 Describe(".New", func() { 56 It("returns a new BlobStore with the provided credentials, region, and bucket", func() { 57 Expect(*blobStore.S3.Config.Region).To(Equal("some-s3-region")) 58 Expect(*blobStore.S3.Config.S3ForcePathStyle).To(BeTrue()) 59 Expect(blobStore.S3.Config.Credentials.Get()).To(Equal(credentials.Value{ 60 AccessKeyID: "some-access-key", 61 SecretAccessKey: "some-secret-key", 62 })) 63 Expect(blobStore.Bucket).To(Equal("bucket")) 64 }) 65 }) 66 67 Describe("#List", func() { 68 It("lists objects in a bucket", func() { 69 responseBody := ` 70 <?xml version="1.0" encoding="UTF-8"?> 71 <ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> 72 <Name>bucket</Name> 73 <Prefix/> 74 <Marker/> 75 <MaxKeys>1000</MaxKeys> 76 <IsTruncated>false</IsTruncated> 77 <Contents> 78 <Key>my-image.jpg</Key> 79 <LastModified>2009-10-12T17:50:30.000Z</LastModified> 80 <ETag>"fba9dede5f27731c9771645a39863328"</ETag> 81 <Size>434234</Size> 82 <StorageClass>STANDARD</StorageClass> 83 <Owner> 84 <ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID> 85 <DisplayName>mtd@amazon.com</DisplayName> 86 </Owner> 87 </Contents> 88 <Contents> 89 <Key>my-third-image.jpg</Key> 90 <LastModified>2009-10-12T17:50:30.000Z</LastModified> 91 <ETag>"1b2cf535f27731c974343645a3985328"</ETag> 92 <Size>64994</Size> 93 <StorageClass>STANDARD</StorageClass> 94 <Owner> 95 <ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID> 96 <DisplayName>mtd@amazon.com</DisplayName> 97 </Owner> 98 </Contents> 99 </ListBucketResult> 100 ` 101 102 fakeServer.AppendHandlers(ghttp.CombineHandlers( 103 ghttp.VerifyRequest("GET", "/bucket"), 104 ghttp.RespondWith(http.StatusOK, responseBody, http.Header{"Content-Type": []string{"application/xml"}}), 105 )) 106 107 expectedTime, err := time.Parse(time.RFC3339Nano, "2009-10-12T17:50:30.000Z") 108 Expect(err).NotTo(HaveOccurred()) 109 110 Expect(blobStore.List()).To(Equal([]blob.Blob{ 111 {Path: "my-image.jpg", Size: 434234, Created: expectedTime}, 112 {Path: "my-third-image.jpg", Size: 64994, Created: expectedTime}, 113 })) 114 115 Expect(fakeServer.ReceivedRequests()).To(HaveLen(1)) 116 }) 117 118 It("returns an error when we fail to retrieve the objects from S3", func() { 119 fakeServer.AppendHandlers(ghttp.CombineHandlers( 120 ghttp.VerifyRequest("GET", "/bucket"), 121 ghttp.RespondWith(http.StatusInternalServerError, nil, http.Header{"Content-Type": []string{"application/xml"}}), 122 )) 123 124 _, err := blobStore.List() 125 Expect(err).To(MatchError(ContainSubstring("500 Internal Server Error"))) 126 }) 127 }) 128 129 Describe("#Upload", func() { 130 It("uploads the provided reader into the bucket", func() { 131 fakeServer.AppendHandlers(ghttp.CombineHandlers( 132 ghttp.VerifyRequest("PUT", "/bucket/some-path/some-object"), 133 ghttp.VerifyHeader(http.Header{"X-Amz-Acl": []string{"private"}}), 134 func(_ http.ResponseWriter, request *http.Request) { 135 Expect(ioutil.ReadAll(request.Body)).To(Equal([]byte("some data"))) 136 }, 137 ghttp.RespondWith(http.StatusOK, "", http.Header{}), 138 )) 139 140 Expect(blobStore.Upload("some-path/some-object", strings.NewReader("some data"))).To(Succeed()) 141 142 Expect(fakeServer.ReceivedRequests()).To(HaveLen(1)) 143 }) 144 145 It("returns an error when S3 fail to receive the object", func() { 146 fakeServer.AppendHandlers(ghttp.CombineHandlers( 147 ghttp.VerifyRequest("PUT", "/bucket/some-path/some-object"), 148 ghttp.RespondWith(http.StatusInternalServerError, "", http.Header{}), 149 )) 150 151 err := blobStore.Upload("some-path/some-object", strings.NewReader("some data")) 152 Expect(err).To(MatchError(ContainSubstring("500 Internal Server Error"))) 153 }) 154 }) 155 156 Describe("#Download", func() { 157 It("dowloads the requested path", func() { 158 fakeServer.AppendHandlers(ghttp.CombineHandlers( 159 ghttp.VerifyRequest("GET", "/bucket/some-path/some-object"), 160 ghttp.RespondWith(http.StatusOK, "some data", http.Header{"Content-Length": []string{"9"}}), 161 )) 162 163 pathReader, err := blobStore.Download("some-path/some-object") 164 Expect(err).NotTo(HaveOccurred()) 165 Expect(ioutil.ReadAll(pathReader)).To(Equal([]byte("some data"))) 166 Expect(pathReader.Close()).To(Succeed()) 167 168 Expect(fakeServer.ReceivedRequests()).To(HaveLen(1)) 169 }) 170 171 It("returns an error when S3 fails to retrieve the object", func() { 172 fakeServer.AppendHandlers(ghttp.CombineHandlers( 173 ghttp.VerifyRequest("GET", "/bucket/some-path/some-object"), 174 ghttp.RespondWith(http.StatusInternalServerError, "", http.Header{}), 175 )) 176 177 _, err := blobStore.Download("some-path/some-object") 178 Expect(err).To(MatchError(ContainSubstring("500 Internal Server Error"))) 179 }) 180 }) 181 182 Describe("#Delete", func() { 183 It("deletes the object at the provided path", func() { 184 fakeServer.AppendHandlers(ghttp.CombineHandlers( 185 ghttp.VerifyRequest("DELETE", "/bucket/some-path/some-object"), 186 ghttp.RespondWith(http.StatusNoContent, ""), 187 )) 188 Expect(blobStore.Delete("some-path/some-object")).NotTo(HaveOccurred()) 189 Expect(fakeServer.ReceivedRequests()).To(HaveLen(1)) 190 }) 191 192 It("returns an error when S3 fails to delete the object", func() { 193 fakeServer.AppendHandlers(ghttp.CombineHandlers( 194 ghttp.VerifyRequest("DELETE", "/bucket/some-path/some-object"), 195 ghttp.RespondWith(http.StatusInternalServerError, "", http.Header{}), 196 )) 197 198 err := blobStore.Delete("some-path/some-object") 199 Expect(err).To(MatchError(ContainSubstring("500 Internal Server Error"))) 200 }) 201 }) 202 203 Context("Droplet Actions", func() { 204 Describe("#DownloadAppBitsAction", func() { 205 It("constructs the correct Action to download app bits", func() { 206 Expect(blobStore.DownloadAppBitsAction("droplet-name")).To(Equal(models.WrapAction(&models.SerialAction{ 207 LogSource: "DROPLET", 208 Actions: []*models.Action{ 209 models.WrapAction(&models.RunAction{ 210 Path: "/tmp/s3tool", 211 Dir: "/", 212 Args: []string{ 213 "get", 214 "some-access-key", 215 "some-secret-key", 216 "bucket", 217 "some-s3-region", 218 "/droplet-name-bits.zip", 219 "/tmp/bits.zip", 220 }, 221 User: "vcap", 222 }), 223 models.WrapAction(&models.RunAction{ 224 Path: "/bin/mkdir", 225 Args: []string{"/tmp/app"}, 226 User: "vcap", 227 }), 228 models.WrapAction(&models.RunAction{ 229 Path: "/usr/bin/unzip", 230 Dir: "/tmp/app", 231 Args: []string{"-q", "/tmp/bits.zip"}, 232 User: "vcap", 233 }), 234 }, 235 }))) 236 }) 237 }) 238 239 Describe("#DeleteAppBitsAction", func() { 240 It("constructs the correct Action to delete app bits", func() { 241 Expect(blobStore.DeleteAppBitsAction("droplet-name")).To(Equal(models.WrapAction(&models.RunAction{ 242 Path: "/tmp/s3tool", 243 Dir: "/", 244 Args: []string{ 245 "delete", 246 "some-access-key", 247 "some-secret-key", 248 "bucket", 249 "some-s3-region", 250 "/droplet-name-bits.zip", 251 }, 252 User: "vcap", 253 LogSource: "DROPLET", 254 }))) 255 }) 256 }) 257 258 Describe("#UploadDropletAction", func() { 259 It("constructs the correct Action to upload the droplet", func() { 260 Expect(blobStore.UploadDropletAction("droplet-name")).To(Equal(models.WrapAction(&models.RunAction{ 261 Path: "/tmp/s3tool", 262 Dir: "/", 263 Args: []string{ 264 "put", 265 "some-access-key", 266 "some-secret-key", 267 "bucket", 268 "some-s3-region", 269 "/droplet-name-droplet.tgz", 270 "/tmp/droplet", 271 }, 272 User: "vcap", 273 LogSource: "DROPLET", 274 }))) 275 }) 276 }) 277 278 Describe("#DownloadDropletAction", func() { 279 It("constructs the correct Action to download the droplet", func() { 280 Expect(blobStore.DownloadDropletAction("droplet-name")).To(Equal(models.WrapAction(&models.SerialAction{ 281 LogSource: "DROPLET", 282 Actions: []*models.Action{ 283 models.WrapAction(&models.RunAction{ 284 Path: "/tmp/s3tool", 285 Dir: "/", 286 Args: []string{ 287 "get", 288 "some-access-key", 289 "some-secret-key", 290 "bucket", 291 "some-s3-region", 292 "/droplet-name-droplet.tgz", 293 "/tmp/droplet.tgz", 294 }, 295 User: "vcap", 296 }), 297 models.WrapAction(&models.RunAction{ 298 Path: "/bin/tar", 299 Args: []string{"zxf", "/tmp/droplet.tgz"}, 300 Dir: "/home/vcap", 301 User: "vcap", 302 }), 303 }, 304 }))) 305 }) 306 }) 307 }) 308 })