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 })