github.com/arunkumar7540/cli@v6.45.0+incompatible/api/cloudcontroller/ccv3/droplet_test.go (about) 1 package ccv3_test 2 3 import ( 4 "code.cloudfoundry.org/cli/api/cloudcontroller" 5 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/ccv3fakes" 6 "code.cloudfoundry.org/cli/api/cloudcontroller/wrapper" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "mime/multipart" 12 "net/http" 13 "strings" 14 15 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 16 . "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" 17 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 18 . "github.com/onsi/ginkgo" 19 . "github.com/onsi/gomega" 20 . "github.com/onsi/gomega/ghttp" 21 ) 22 23 var _ = Describe("Droplet", func() { 24 var client *Client 25 26 BeforeEach(func() { 27 client, _ = NewTestClient() 28 }) 29 30 Describe("CreateDroplet", func() { 31 var ( 32 droplet Droplet 33 warnings Warnings 34 executeErr error 35 ) 36 37 JustBeforeEach(func() { 38 droplet, warnings, executeErr = client.CreateDroplet("app-guid") 39 }) 40 41 When("the request succeeds", func() { 42 BeforeEach(func() { 43 response := `{ 44 "guid": "some-guid", 45 "state": "AWAITING_UPLOAD", 46 "error": null, 47 "lifecycle": { 48 "type": "buildpack", 49 "data": {} 50 }, 51 "buildpacks": [ 52 { 53 "name": "some-buildpack", 54 "detect_output": "detected-buildpack" 55 } 56 ], 57 "image": "docker/some-image", 58 "stack": "some-stack", 59 "created_at": "2016-03-28T23:39:34Z", 60 "updated_at": "2016-03-28T23:39:47Z" 61 }` 62 server.AppendHandlers( 63 CombineHandlers( 64 VerifyRequest(http.MethodPost, "/v3/droplets"), 65 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"warning-1"}}), 66 ), 67 ) 68 }) 69 70 It("returns the given droplet and all warnings", func() { 71 Expect(executeErr).ToNot(HaveOccurred()) 72 73 Expect(droplet).To(Equal(Droplet{ 74 GUID: "some-guid", 75 Stack: "some-stack", 76 State: constant.DropletAwaitingUpload, 77 Buildpacks: []DropletBuildpack{ 78 { 79 Name: "some-buildpack", 80 DetectOutput: "detected-buildpack", 81 }, 82 }, 83 Image: "docker/some-image", 84 CreatedAt: "2016-03-28T23:39:34Z", 85 })) 86 Expect(warnings).To(ConsistOf("warning-1")) 87 }) 88 }) 89 90 When("cloud controller returns an error", func() { 91 BeforeEach(func() { 92 response := `{ 93 "errors": [ 94 { 95 "code": 10010, 96 "detail": "Droplet not found", 97 "title": "CF-ResourceNotFound" 98 } 99 ] 100 }` 101 server.AppendHandlers( 102 CombineHandlers( 103 VerifyRequest(http.MethodPost, "/v3/droplets"), 104 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"warning-1"}}), 105 ), 106 ) 107 }) 108 109 It("returns the error", func() { 110 Expect(executeErr).To(MatchError(ccerror.DropletNotFoundError{})) 111 Expect(warnings).To(ConsistOf("warning-1")) 112 }) 113 }) 114 }) 115 116 Describe("GetApplicationDropletCurrent", func() { 117 var ( 118 droplet Droplet 119 warnings Warnings 120 executeErr error 121 ) 122 123 JustBeforeEach(func() { 124 droplet, warnings, executeErr = client.GetApplicationDropletCurrent("some-guid") 125 }) 126 127 When("the request succeeds", func() { 128 BeforeEach(func() { 129 response := `{ 130 "guid": "some-guid", 131 "state": "STAGED", 132 "error": null, 133 "lifecycle": { 134 "type": "buildpack", 135 "data": {} 136 }, 137 "buildpacks": [ 138 { 139 "name": "some-buildpack", 140 "detect_output": "detected-buildpack" 141 } 142 ], 143 "image": "docker/some-image", 144 "stack": "some-stack", 145 "created_at": "2016-03-28T23:39:34Z", 146 "updated_at": "2016-03-28T23:39:47Z" 147 }` 148 server.AppendHandlers( 149 CombineHandlers( 150 VerifyRequest(http.MethodGet, "/v3/apps/some-guid/droplets/current"), 151 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"warning-1"}}), 152 ), 153 ) 154 }) 155 156 It("returns the given droplet and all warnings", func() { 157 Expect(executeErr).ToNot(HaveOccurred()) 158 159 Expect(droplet).To(Equal(Droplet{ 160 GUID: "some-guid", 161 Stack: "some-stack", 162 State: constant.DropletStaged, 163 Buildpacks: []DropletBuildpack{ 164 { 165 Name: "some-buildpack", 166 DetectOutput: "detected-buildpack", 167 }, 168 }, 169 Image: "docker/some-image", 170 CreatedAt: "2016-03-28T23:39:34Z", 171 })) 172 Expect(warnings).To(ConsistOf("warning-1")) 173 }) 174 }) 175 176 When("cloud controller returns an error", func() { 177 BeforeEach(func() { 178 response := `{ 179 "errors": [ 180 { 181 "code": 10010, 182 "detail": "Droplet not found", 183 "title": "CF-ResourceNotFound" 184 } 185 ] 186 }` 187 server.AppendHandlers( 188 CombineHandlers( 189 VerifyRequest(http.MethodGet, "/v3/apps/some-guid/droplets/current"), 190 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"warning-1"}}), 191 ), 192 ) 193 }) 194 195 It("returns the error and all given warnings", func() { 196 Expect(executeErr).To(MatchError(ccerror.DropletNotFoundError{})) 197 Expect(warnings).To(ConsistOf("warning-1")) 198 }) 199 }) 200 }) 201 202 Describe("GetDroplet", func() { 203 var ( 204 droplet Droplet 205 warnings Warnings 206 executeErr error 207 ) 208 209 JustBeforeEach(func() { 210 droplet, warnings, executeErr = client.GetDroplet("some-guid") 211 }) 212 213 When("the request succeeds", func() { 214 BeforeEach(func() { 215 response := `{ 216 "guid": "some-guid", 217 "state": "STAGED", 218 "error": null, 219 "lifecycle": { 220 "type": "buildpack", 221 "data": {} 222 }, 223 "buildpacks": [ 224 { 225 "name": "some-buildpack", 226 "detect_output": "detected-buildpack" 227 } 228 ], 229 "image": "docker/some-image", 230 "stack": "some-stack", 231 "created_at": "2016-03-28T23:39:34Z", 232 "updated_at": "2016-03-28T23:39:47Z" 233 }` 234 server.AppendHandlers( 235 CombineHandlers( 236 VerifyRequest(http.MethodGet, "/v3/droplets/some-guid"), 237 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"warning-1"}}), 238 ), 239 ) 240 }) 241 242 It("returns the given droplet and all warnings", func() { 243 Expect(executeErr).ToNot(HaveOccurred()) 244 245 Expect(droplet).To(Equal(Droplet{ 246 GUID: "some-guid", 247 Stack: "some-stack", 248 State: constant.DropletStaged, 249 Buildpacks: []DropletBuildpack{ 250 { 251 Name: "some-buildpack", 252 DetectOutput: "detected-buildpack", 253 }, 254 }, 255 Image: "docker/some-image", 256 CreatedAt: "2016-03-28T23:39:34Z", 257 })) 258 Expect(warnings).To(ConsistOf("warning-1")) 259 }) 260 }) 261 262 When("cloud controller returns an error", func() { 263 BeforeEach(func() { 264 response := `{ 265 "errors": [ 266 { 267 "code": 10010, 268 "detail": "Droplet not found", 269 "title": "CF-ResourceNotFound" 270 } 271 ] 272 }` 273 server.AppendHandlers( 274 CombineHandlers( 275 VerifyRequest(http.MethodGet, "/v3/droplets/some-guid"), 276 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"warning-1"}}), 277 ), 278 ) 279 }) 280 281 It("returns the error", func() { 282 Expect(executeErr).To(MatchError(ccerror.DropletNotFoundError{})) 283 Expect(warnings).To(ConsistOf("warning-1")) 284 }) 285 }) 286 }) 287 288 Describe("GetDroplets", func() { 289 var ( 290 droplets []Droplet 291 warnings Warnings 292 executeErr error 293 ) 294 295 JustBeforeEach(func() { 296 droplets, warnings, executeErr = client.GetDroplets( 297 Query{Key: AppGUIDFilter, Values: []string{"some-app-guid"}}, 298 Query{Key: PerPage, Values: []string{"2"}}, 299 ) 300 }) 301 302 When("the CC returns back droplets", func() { 303 BeforeEach(func() { 304 response1 := fmt.Sprintf(`{ 305 "pagination": { 306 "next": { 307 "href": "%s/v3/droplets?app_guids=some-app-guid&per_page=2&page=2" 308 } 309 }, 310 "resources": [ 311 { 312 "guid": "some-guid-1", 313 "stack": "some-stack-1", 314 "buildpacks": [{ 315 "name": "some-buildpack-1", 316 "detect_output": "detected-buildpack-1" 317 }], 318 "state": "STAGED", 319 "created_at": "2017-08-16T00:18:24Z", 320 "links": { 321 "package": "https://api.com/v3/packages/some-package-guid" 322 } 323 }, 324 { 325 "guid": "some-guid-2", 326 "stack": "some-stack-2", 327 "buildpacks": [{ 328 "name": "some-buildpack-2", 329 "detect_output": "detected-buildpack-2" 330 }], 331 "state": "COPYING", 332 "created_at": "2017-08-16T00:19:05Z" 333 } 334 ] 335 }`, server.URL()) 336 response2 := `{ 337 "pagination": { 338 "next": null 339 }, 340 "resources": [ 341 { 342 "guid": "some-guid-3", 343 "stack": "some-stack-3", 344 "buildpacks": [{ 345 "name": "some-buildpack-3", 346 "detect_output": "detected-buildpack-3" 347 }], 348 "state": "FAILED", 349 "created_at": "2017-08-22T17:55:02Z" 350 } 351 ] 352 }` 353 server.AppendHandlers( 354 CombineHandlers( 355 VerifyRequest(http.MethodGet, "/v3/droplets", "app_guids=some-app-guid&per_page=2"), 356 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 357 ), 358 ) 359 server.AppendHandlers( 360 CombineHandlers( 361 VerifyRequest(http.MethodGet, "/v3/droplets", "app_guids=some-app-guid&per_page=2&page=2"), 362 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 363 ), 364 ) 365 }) 366 367 It("returns the droplets and all warnings", func() { 368 Expect(executeErr).ToNot(HaveOccurred()) 369 Expect(droplets).To(HaveLen(3)) 370 371 Expect(droplets[0]).To(Equal(Droplet{ 372 GUID: "some-guid-1", 373 Stack: "some-stack-1", 374 State: constant.DropletStaged, 375 Buildpacks: []DropletBuildpack{ 376 { 377 Name: "some-buildpack-1", 378 DetectOutput: "detected-buildpack-1", 379 }, 380 }, 381 CreatedAt: "2017-08-16T00:18:24Z", 382 })) 383 Expect(droplets[1]).To(Equal(Droplet{ 384 GUID: "some-guid-2", 385 Stack: "some-stack-2", 386 State: constant.DropletCopying, 387 Buildpacks: []DropletBuildpack{ 388 { 389 Name: "some-buildpack-2", 390 DetectOutput: "detected-buildpack-2", 391 }, 392 }, 393 CreatedAt: "2017-08-16T00:19:05Z", 394 })) 395 Expect(droplets[2]).To(Equal(Droplet{ 396 GUID: "some-guid-3", 397 Stack: "some-stack-3", 398 State: constant.DropletFailed, 399 Buildpacks: []DropletBuildpack{ 400 { 401 Name: "some-buildpack-3", 402 DetectOutput: "detected-buildpack-3", 403 }, 404 }, 405 CreatedAt: "2017-08-22T17:55:02Z", 406 })) 407 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 408 }) 409 }) 410 411 When("cloud controller returns an error", func() { 412 BeforeEach(func() { 413 response := `{ 414 "errors": [ 415 { 416 "code": 10010, 417 "detail": "App not found", 418 "title": "CF-ResourceNotFound" 419 } 420 ] 421 }` 422 server.AppendHandlers( 423 CombineHandlers( 424 VerifyRequest(http.MethodGet, "/v3/droplets", "app_guids=some-app-guid&per_page=2"), 425 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"warning-1"}}), 426 ), 427 ) 428 }) 429 430 It("returns the error", func() { 431 Expect(executeErr).To(MatchError(ccerror.ApplicationNotFoundError{})) 432 Expect(warnings).To(ConsistOf("warning-1")) 433 }) 434 }) 435 }) 436 437 Describe("UploadDropletBits", func() { 438 var ( 439 dropletGUID string 440 dropletFile io.Reader 441 dropletFilePath string 442 dropletContent string 443 jobURL JobURL 444 warnings Warnings 445 executeErr error 446 ) 447 448 BeforeEach(func() { 449 dropletGUID = "some-droplet-guid" 450 dropletContent = "some-content" 451 dropletFile = strings.NewReader(dropletContent) 452 dropletFilePath = "some/fake-droplet.tgz" 453 }) 454 455 JustBeforeEach(func() { 456 jobURL, warnings, executeErr = client.UploadDropletBits(dropletGUID, dropletFilePath, dropletFile, int64(len(dropletContent))) 457 }) 458 459 When("the upload is successful", func() { 460 BeforeEach(func() { 461 response := `{ 462 "guid": "some-droplet-guid", 463 "state": "PROCESSING_UPLOAD" 464 }` 465 466 verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) { 467 contentType := req.Header.Get("Content-Type") 468 Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+")) 469 470 defer req.Body.Close() 471 requestReader := multipart.NewReader(req.Body, contentType[30:]) 472 473 dropletPart, err := requestReader.NextPart() 474 Expect(err).NotTo(HaveOccurred()) 475 476 Expect(dropletPart.FormName()).To(Equal("bits")) 477 Expect(dropletPart.FileName()).To(Equal("fake-droplet.tgz")) 478 479 defer dropletPart.Close() 480 partContents, err := ioutil.ReadAll(dropletPart) 481 Expect(err).ToNot(HaveOccurred()) 482 Expect(string(partContents)).To(Equal(dropletContent)) 483 } 484 485 server.AppendHandlers( 486 CombineHandlers( 487 VerifyRequest(http.MethodPost, "/v3/droplets/some-droplet-guid/upload"), 488 verifyHeaderAndBody, 489 RespondWith( 490 http.StatusAccepted, 491 response, 492 http.Header{ 493 "X-Cf-Warnings": {"this is a warning"}, 494 "Location": {"http://example.com/job-guid"}, 495 }, 496 ), 497 ), 498 ) 499 }) 500 501 It("returns the processing job URL and warnings", func() { 502 Expect(executeErr).ToNot(HaveOccurred()) 503 Expect(warnings).To(ConsistOf(Warnings{"this is a warning"})) 504 Expect(jobURL).To(Equal(JobURL("http://example.com/job-guid"))) 505 }) 506 }) 507 508 When("there is an error reading the buildpack", func() { 509 var ( 510 fakeReader *ccv3fakes.FakeReader 511 expectedErr error 512 ) 513 514 BeforeEach(func() { 515 expectedErr = errors.New("droplet read error") 516 fakeReader = new(ccv3fakes.FakeReader) 517 fakeReader.ReadReturns(0, expectedErr) 518 dropletFile = fakeReader 519 520 server.AppendHandlers( 521 VerifyRequest(http.MethodPost, "/v3/droplets/some-droplet-guid/upload"), 522 ) 523 }) 524 525 It("returns the error", func() { 526 Expect(executeErr).To(MatchError(expectedErr)) 527 }) 528 }) 529 530 When("the upload returns an error", func() { 531 BeforeEach(func() { 532 response := `{ 533 "errors": [{ 534 "detail": "The droplet could not be found: some-droplet-guid", 535 "title": "CF-ResourceNotFound", 536 "code": 10010 537 }] 538 }` 539 540 server.AppendHandlers( 541 CombineHandlers( 542 VerifyRequest(http.MethodPost, "/v3/droplets/some-droplet-guid/upload"), 543 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 544 ), 545 ) 546 }) 547 548 It("returns the error and warnings", func() { 549 Expect(executeErr).To(MatchError( 550 ccerror.ResourceNotFoundError{ 551 Message: "The droplet could not be found: some-droplet-guid", 552 }, 553 )) 554 Expect(warnings).To(ConsistOf(Warnings{"this is a warning"})) 555 }) 556 }) 557 558 When("cloud controller returns an error", func() { 559 BeforeEach(func() { 560 dropletGUID = "some-guid" 561 562 response := `{ 563 "errors": [ 564 { 565 "code": 10010, 566 "detail": "Droplet not found", 567 "title": "CF-ResourceNotFound" 568 } 569 ] 570 }` 571 server.AppendHandlers( 572 CombineHandlers( 573 VerifyRequest(http.MethodPost, "/v3/droplets/some-guid/upload"), 574 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"warning-1"}}), 575 ), 576 ) 577 }) 578 579 It("returns the error", func() { 580 Expect(executeErr).To(MatchError(ccerror.DropletNotFoundError{})) 581 Expect(warnings).To(ConsistOf("warning-1")) 582 }) 583 }) 584 585 When("a retryable error occurs", func() { 586 BeforeEach(func() { 587 wrapper := &wrapper.CustomWrapper{ 588 CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error { 589 defer GinkgoRecover() // Since this will be running in a thread 590 591 if strings.HasSuffix(request.URL.String(), "/v3/droplets/some-droplet-guid/upload") { 592 _, err := ioutil.ReadAll(request.Body) 593 Expect(err).ToNot(HaveOccurred()) 594 Expect(request.Body.Close()).ToNot(HaveOccurred()) 595 return request.ResetBody() 596 } 597 return connection.Make(request, response) 598 }, 599 } 600 601 client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}}) 602 }) 603 604 It("returns the PipeSeekError", func() { 605 Expect(executeErr).To(MatchError(ccerror.PipeSeekError{})) 606 }) 607 }) 608 609 When("an http error occurs mid-transfer", func() { 610 var expectedErr error 611 612 BeforeEach(func() { 613 expectedErr = errors.New("some read error") 614 615 wrapper := &wrapper.CustomWrapper{ 616 CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error { 617 defer GinkgoRecover() // Since this will be running in a thread 618 619 if strings.HasSuffix(request.URL.String(), "/v3/droplets/some-droplet-guid/upload") { 620 defer request.Body.Close() 621 readBytes, err := ioutil.ReadAll(request.Body) 622 Expect(err).ToNot(HaveOccurred()) 623 Expect(len(readBytes)).To(BeNumerically(">", len(dropletContent))) 624 return expectedErr 625 } 626 return connection.Make(request, response) 627 }, 628 } 629 630 client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}}) 631 }) 632 633 It("returns the http error", func() { 634 Expect(executeErr).To(MatchError(expectedErr)) 635 }) 636 }) 637 }) 638 })