github.com/cloudfoundry-attic/ltc@v0.0.0-20151123212628-098adc7919fc/blob_store/dav_blob_store/blob_store_test.go (about)

     1  package dav_blob_store_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"reflect"
    11  	"strings"
    12  	"time"
    13  
    14  	. "github.com/onsi/ginkgo"
    15  	. "github.com/onsi/gomega"
    16  	"github.com/onsi/gomega/ghttp"
    17  
    18  	"github.com/cloudfoundry-incubator/bbs/models"
    19  	"github.com/cloudfoundry-incubator/ltc/blob_store/blob"
    20  	"github.com/cloudfoundry-incubator/ltc/blob_store/dav_blob_store"
    21  	config_package "github.com/cloudfoundry-incubator/ltc/config"
    22  )
    23  
    24  var _ = Describe("BlobStore", func() {
    25  	var (
    26  		blobStore      *dav_blob_store.BlobStore
    27  		fakeServer     *ghttp.Server
    28  		blobTargetInfo config_package.BlobStoreConfig
    29  	)
    30  
    31  	BeforeEach(func() {
    32  		fakeServer = ghttp.NewServer()
    33  		fakeServerURL, err := url.Parse(fakeServer.URL())
    34  		Expect(err).NotTo(HaveOccurred())
    35  
    36  		serverHost, serverPort, err := net.SplitHostPort(fakeServerURL.Host)
    37  		Expect(err).NotTo(HaveOccurred())
    38  
    39  		blobTargetInfo = config_package.BlobStoreConfig{
    40  			Host:     serverHost,
    41  			Port:     serverPort,
    42  			Username: "user",
    43  			Password: "pass",
    44  		}
    45  
    46  		blobStore = dav_blob_store.New(blobTargetInfo)
    47  	})
    48  
    49  	AfterEach(func() {
    50  		if fakeServer != nil {
    51  			fakeServer.Close()
    52  		}
    53  	})
    54  
    55  	Describe("#List", func() {
    56  		var responseBodyRoot string
    57  		BeforeEach(func() {
    58  			responseBodyRoot = `
    59  				<?xml version="1.0" encoding="utf-8"?>
    60  				<D:multistatus xmlns:D="DAV:" xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
    61  				  <D:response>
    62  					<D:href>http://192.168.11.11:8444/blobs/a-droplet.tgz</D:href>
    63  					<D:propstat>
    64  					  <D:prop>
    65  						<D:creationdate ns0:dt="dateTime.tz">2015-07-29T18:43:50Z</D:creationdate>
    66  						<D:getcontentlanguage>en</D:getcontentlanguage>
    67  						<D:getcontentlength>4096</D:getcontentlength>
    68  						<D:getcontenttype>application/x-gtar-compressed</D:getcontenttype>
    69  						<D:getlastmodified ns0:dt="dateTime.rfc1123">Wed, 29 Jul 2015 18:43:36 GMT</D:getlastmodified>
    70  						<D:resourcetype>
    71  						  <D:collection/>
    72  						</D:resourcetype>
    73  					  </D:prop>
    74  					  <D:status>HTTP/1.1 200 OK</D:status>
    75  					</D:propstat>
    76  				  </D:response>
    77  				  <D:response>
    78  					<D:href>http://192.168.11.11:8444/blobs/b-droplet.tgz</D:href>
    79  					<D:propstat>
    80  					  <D:prop>
    81  						<D:creationdate ns0:dt="dateTime.tz">2015-07-29T18:43:50Z</D:creationdate>
    82  						<D:getcontentlanguage>en</D:getcontentlanguage>
    83  						<D:getcontentlength>4096</D:getcontentlength>
    84  						<D:getcontenttype>application/x-gtar-compressed</D:getcontenttype>
    85  						<D:getlastmodified ns0:dt="dateTime.rfc1123">Wed, 29 Jul 2015 18:43:36 GMT</D:getlastmodified>
    86  						<D:resourcetype>
    87  						  <D:collection/>
    88  						</D:resourcetype>
    89  					  </D:prop>
    90  					  <D:status>HTTP/1.1 200 OK</D:status>
    91  					</D:propstat>
    92  				  </D:response>
    93  				  <D:response>
    94  					<D:href>http://192.168.11.11:8444/blobs/c-droplet.tgz</D:href>
    95  					<D:propstat>
    96  					  <D:prop>
    97  						<D:creationdate ns0:dt="dateTime.tz">2015-07-29T18:43:50Z</D:creationdate>
    98  						<D:getcontentlanguage>en</D:getcontentlanguage>
    99  						<D:getcontentlength>4096</D:getcontentlength>
   100  						<D:getcontenttype>application/x-gtar-compressed</D:getcontenttype>
   101  						<D:getlastmodified ns0:dt="dateTime.rfc1123">Wed, 29 Jul 2015 18:43:36 GMT</D:getlastmodified>
   102  						<D:resourcetype>
   103  						  <D:collection/>
   104  						</D:resourcetype>
   105  					  </D:prop>
   106  					  <D:status>HTTP/1.1 200 OK</D:status>
   107  					</D:propstat>
   108  				  </D:response>
   109  				</D:multistatus>
   110  			`
   111  
   112  			responseBodyRoot = strings.Replace(responseBodyRoot, "http://192.168.11.11:8444", fakeServer.URL(), -1)
   113  		})
   114  
   115  		It("lists objects", func() {
   116  			fakeServer.RouteToHandler("PROPFIND", "/blobs", ghttp.CombineHandlers(
   117  				ghttp.VerifyHeaderKV("Depth", "1"),
   118  				ghttp.VerifyBasicAuth("user", "pass"),
   119  				ghttp.RespondWith(207, responseBodyRoot, http.Header{"Content-Type": []string{"application/xml"}}),
   120  			))
   121  
   122  			expectedTime, err := time.Parse(time.RFC1123, "Wed, 29 Jul 2015 18:43:36 GMT")
   123  			Expect(err).NotTo(HaveOccurred())
   124  
   125  			Expect(blobStore.List()).To(ConsistOf(
   126  				blob.Blob{Path: "b-droplet.tgz", Size: 4096, Created: expectedTime},
   127  				blob.Blob{Path: "a-droplet.tgz", Size: 4096, Created: expectedTime},
   128  				blob.Blob{Path: "c-droplet.tgz", Size: 4096, Created: expectedTime},
   129  			))
   130  
   131  			Expect(fakeServer.ReceivedRequests()).To(HaveLen(1))
   132  		})
   133  
   134  		Context("when the list call fails", func() {
   135  			It("returns an error it can't connect to the server", func() {
   136  				fakeServer.Close()
   137  				fakeServer = nil
   138  
   139  				_, err := blobStore.List()
   140  				Expect(reflect.TypeOf(err).String()).To(Equal("*net.OpError"))
   141  			})
   142  
   143  			It("returns an error when we fail to retrieve the objects from DAV", func() {
   144  				fakeServer.AppendHandlers(ghttp.CombineHandlers(
   145  					ghttp.VerifyRequest("PROPFIND", "/blobs"),
   146  					ghttp.VerifyHeaderKV("Depth", "1"),
   147  					ghttp.VerifyBasicAuth("user", "pass"),
   148  					ghttp.RespondWith(http.StatusInternalServerError, nil, http.Header{"Content-Type": []string{"application/xml"}}),
   149  				))
   150  
   151  				_, err := blobStore.List()
   152  				Expect(err).To(MatchError(ContainSubstring("Internal Server Error")))
   153  
   154  				Expect(fakeServer.ReceivedRequests()).To(HaveLen(1))
   155  			})
   156  
   157  			It("returns an error when it fails to parse the XML", func() {
   158  				fakeServer.RouteToHandler("PROPFIND", "/blobs", ghttp.CombineHandlers(
   159  					ghttp.VerifyHeaderKV("Depth", "1"),
   160  					ghttp.VerifyBasicAuth("user", "pass"),
   161  					ghttp.RespondWith(207, `<D:bad`, http.Header{"Content-Type": []string{"application/xml"}}),
   162  				))
   163  
   164  				_, err := blobStore.List()
   165  				Expect(err).To(MatchError("XML syntax error on line 1: unexpected EOF"))
   166  
   167  				Expect(fakeServer.ReceivedRequests()).To(HaveLen(1))
   168  			})
   169  
   170  			Context("when it fails to parse the time", func() {
   171  				It("returns an error", func() {
   172  					responseBodyRoot = strings.Replace(responseBodyRoot, "Wed, 29 Jul 2015 18:43:36 GMT", "ABC", -1)
   173  
   174  					fakeServer.RouteToHandler("PROPFIND", "/blobs", ghttp.CombineHandlers(
   175  						ghttp.VerifyHeaderKV("Depth", "1"),
   176  						ghttp.VerifyBasicAuth("user", "pass"),
   177  						ghttp.RespondWith(207, responseBodyRoot, http.Header{"Content-Type": []string{"application/xml"}}),
   178  					))
   179  
   180  					_, err := blobStore.List()
   181  					Expect(err).To(MatchError(ContainSubstring(`cannot parse "ABC"`)))
   182  
   183  					Expect(fakeServer.ReceivedRequests()).To(HaveLen(1))
   184  				})
   185  			})
   186  		})
   187  
   188  		It("uses the correct HTTP client with a specified timeout", func() {
   189  			defaultTransport := blobStore.Client.Transport
   190  			defer func() { blobStore.Client.Transport = defaultTransport }()
   191  
   192  			usedClient := false
   193  			blobStore.Client.Transport = &http.Transport{
   194  				Proxy: func(request *http.Request) (*url.URL, error) {
   195  					usedClient = true
   196  					return nil, errors.New("some error")
   197  				},
   198  			}
   199  
   200  			blobStore.List()
   201  			Expect(usedClient).To(BeTrue())
   202  		})
   203  	})
   204  
   205  	Describe("#Upload", func() {
   206  		It("uploads the provided reader into the collection", func() {
   207  			fakeServer.RouteToHandler("PUT", "/blobs/some-object", ghttp.CombineHandlers(
   208  				ghttp.VerifyBasicAuth("user", "pass"),
   209  				func(_ http.ResponseWriter, request *http.Request) {
   210  					Expect(ioutil.ReadAll(request.Body)).To(Equal([]byte("some data")))
   211  				},
   212  				ghttp.RespondWith(http.StatusCreated, "", http.Header{}),
   213  			))
   214  
   215  			Expect(blobStore.Upload("some-object", strings.NewReader("some data"))).To(Succeed())
   216  
   217  			Expect(fakeServer.ReceivedRequests()).To(HaveLen(1))
   218  		})
   219  
   220  		It("returns an error when DAV fails to receive the object", func() {
   221  			fakeServer.AppendHandlers(ghttp.CombineHandlers(
   222  				ghttp.VerifyRequest("PUT", "/blobs/some-object"),
   223  				ghttp.VerifyBasicAuth("user", "pass"),
   224  				ghttp.RespondWith(http.StatusInternalServerError, "", http.Header{}),
   225  			))
   226  
   227  			err := blobStore.Upload("some-object", strings.NewReader("some data"))
   228  			Expect(err).To(MatchError(ContainSubstring("500 Internal Server Error")))
   229  
   230  			Expect(fakeServer.ReceivedRequests()).To(HaveLen(1))
   231  		})
   232  
   233  		It("returns an error when the DAV client cannot connect", func() {
   234  			fakeServer.Close()
   235  			fakeServer = nil
   236  
   237  			err := blobStore.Upload("some-object", strings.NewReader("some data"))
   238  			Expect(reflect.TypeOf(err).String()).To(Equal("*url.Error"))
   239  		})
   240  	})
   241  
   242  	Describe("#Download", func() {
   243  		It("dowloads the requested path", func() {
   244  			fakeServer.AppendHandlers(ghttp.CombineHandlers(
   245  				ghttp.VerifyRequest("GET", "/blobs/some-object"),
   246  				ghttp.VerifyBasicAuth("user", "pass"),
   247  				ghttp.RespondWith(http.StatusOK, "some data", http.Header{"Content-Length": []string{"9"}}),
   248  			))
   249  
   250  			pathReader, err := blobStore.Download("some-object")
   251  			Expect(err).NotTo(HaveOccurred())
   252  			Expect(ioutil.ReadAll(pathReader)).To(Equal([]byte("some data")))
   253  			Expect(pathReader.Close()).To(Succeed())
   254  
   255  			Expect(fakeServer.ReceivedRequests()).To(HaveLen(1))
   256  		})
   257  
   258  		It("returns an error when DAV fails to retrieve the object", func() {
   259  			fakeServer.AppendHandlers(ghttp.CombineHandlers(
   260  				ghttp.VerifyRequest("GET", "/blobs/some-object"),
   261  				ghttp.VerifyBasicAuth("user", "pass"),
   262  				ghttp.RespondWith(http.StatusInternalServerError, "", http.Header{}),
   263  			))
   264  
   265  			_, err := blobStore.Download("some-object")
   266  			Expect(err).To(MatchError(ContainSubstring("500 Internal Server Error")))
   267  		})
   268  
   269  		It("returns an error when the DAV client cannot connect", func() {
   270  			fakeServer.Close()
   271  			fakeServer = nil
   272  
   273  			_, err := blobStore.Download("some-object")
   274  			Expect(reflect.TypeOf(err).String()).To(Equal("*url.Error"))
   275  		})
   276  	})
   277  
   278  	Describe("#Delete", func() {
   279  		It("deletes the object at the provided path", func() {
   280  			fakeServer.AppendHandlers(ghttp.CombineHandlers(
   281  				ghttp.VerifyRequest("DELETE", "/blobs/some-path/some-object"),
   282  				ghttp.VerifyBasicAuth("user", "pass"),
   283  				ghttp.RespondWith(http.StatusNoContent, ""),
   284  			))
   285  			Expect(blobStore.Delete("some-path/some-object")).NotTo(HaveOccurred())
   286  			Expect(fakeServer.ReceivedRequests()).To(HaveLen(1))
   287  		})
   288  
   289  		It("returns an error when DAV fails to delete the object", func() {
   290  			fakeServer.AppendHandlers(ghttp.CombineHandlers(
   291  				ghttp.VerifyRequest("DELETE", "/blobs/some-path/some-object"),
   292  				ghttp.VerifyBasicAuth("user", "pass"),
   293  				ghttp.RespondWith(http.StatusInternalServerError, "", http.Header{}),
   294  			))
   295  
   296  			err := blobStore.Delete("some-path/some-object")
   297  			Expect(err).To(MatchError(ContainSubstring("500 Internal Server Error")))
   298  		})
   299  
   300  		It("returns an error when the DAV client cannot connect", func() {
   301  			fakeServer.Close()
   302  			fakeServer = nil
   303  
   304  			err := blobStore.Delete("some-path/some-object")
   305  			Expect(reflect.TypeOf(err).String()).To(Equal("*net.OpError"))
   306  		})
   307  	})
   308  
   309  	Context("Droplet Actions", func() {
   310  		var dropletURL string
   311  
   312  		BeforeEach(func() {
   313  			dropletURL = fmt.Sprintf("http://%s:%s@%s:%s/blobs/droplet-name", blobTargetInfo.Username, blobTargetInfo.Password, blobTargetInfo.Host, blobTargetInfo.Port)
   314  		})
   315  
   316  		Describe("#DownloadAppBitsAction", func() {
   317  			It("constructs the correct Action to download app bits", func() {
   318  				Expect(blobStore.DownloadAppBitsAction("droplet-name")).To(Equal(models.WrapAction(&models.DownloadAction{
   319  					From:      dropletURL + "-bits.zip",
   320  					To:        "/tmp/app",
   321  					User:      "vcap",
   322  					LogSource: "DROPLET",
   323  				})))
   324  			})
   325  		})
   326  
   327  		Describe("#DeleteAppBitsAction", func() {
   328  			It("constructs the correct Action to delete app bits", func() {
   329  				Expect(blobStore.DeleteAppBitsAction("droplet-name")).To(Equal(models.WrapAction(&models.RunAction{
   330  					Path:      "/tmp/davtool",
   331  					Dir:       "/",
   332  					Args:      []string{"delete", dropletURL + "-bits.zip"},
   333  					User:      "vcap",
   334  					LogSource: "DROPLET",
   335  				})))
   336  			})
   337  		})
   338  
   339  		Describe("#UploadDropletAction", func() {
   340  			It("constructs the correct Action to upload the droplet", func() {
   341  				Expect(blobStore.UploadDropletAction("droplet-name")).To(Equal(models.WrapAction(&models.RunAction{
   342  					Path:      "/tmp/davtool",
   343  					Dir:       "/",
   344  					Args:      []string{"put", dropletURL + "-droplet.tgz", "/tmp/droplet"},
   345  					User:      "vcap",
   346  					LogSource: "DROPLET",
   347  				})))
   348  			})
   349  		})
   350  
   351  		Describe("#DownloadDropletAction", func() {
   352  			It("constructs the correct Action to download the droplet", func() {
   353  				Expect(blobStore.DownloadDropletAction("droplet-name")).To(Equal(models.WrapAction(&models.DownloadAction{
   354  					From:      dropletURL + "-droplet.tgz",
   355  					To:        "/home/vcap",
   356  					User:      "vcap",
   357  					LogSource: "DROPLET",
   358  				})))
   359  			})
   360  		})
   361  	})
   362  })