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>&quot;fba9dede5f27731c9771645a39863328&quot;</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>&quot;1b2cf535f27731c974343645a3985328&quot;</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  })