github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/api/cloudcontroller/ccv3/process_test.go (about) 1 package ccv3_test 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 8 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 9 . "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 11 "code.cloudfoundry.org/cli/types" 12 . "github.com/onsi/ginkgo" 13 . "github.com/onsi/gomega" 14 . "github.com/onsi/gomega/ghttp" 15 ) 16 17 var _ = Describe("Process", func() { 18 var client *Client 19 20 BeforeEach(func() { 21 client = NewTestClient() 22 }) 23 Describe("MarshalJSON", func() { 24 var ( 25 process Process 26 processBytes []byte 27 err error 28 ) 29 30 BeforeEach(func() { 31 process = Process{} 32 }) 33 34 JustBeforeEach(func() { 35 processBytes, err = process.MarshalJSON() 36 Expect(err).ToNot(HaveOccurred()) 37 }) 38 39 Context("when health check type http is provided", func() { 40 BeforeEach(func() { 41 process = Process{ 42 HealthCheckType: "http", 43 HealthCheckEndpoint: "some-endpoint", 44 } 45 }) 46 47 It("sets the health check type to http and has an endpoint", func() { 48 Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`)) 49 }) 50 }) 51 52 Context("when health check type port is provided", func() { 53 BeforeEach(func() { 54 process = Process{ 55 HealthCheckType: "port", 56 } 57 }) 58 59 It("sets the health check type to port", func() { 60 Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"port", "data": {"endpoint": null}}}`)) 61 }) 62 }) 63 64 Context("when health check type process is provided", func() { 65 BeforeEach(func() { 66 process = Process{ 67 HealthCheckType: "process", 68 } 69 }) 70 71 It("sets the health check type to process", func() { 72 Expect(string(processBytes)).To(MatchJSON(`{"health_check":{"type":"process", "data": {"endpoint": null}}}`)) 73 }) 74 }) 75 }) 76 77 Describe("UnmarshalJSON", func() { 78 var ( 79 process Process 80 processBytes []byte 81 err error 82 ) 83 BeforeEach(func() { 84 processBytes = []byte("{}") 85 }) 86 87 JustBeforeEach(func() { 88 err = json.Unmarshal(processBytes, &process) 89 Expect(err).ToNot(HaveOccurred()) 90 }) 91 Context("when health check type http is provided", func() { 92 BeforeEach(func() { 93 processBytes = []byte(`{"health_check":{"type":"http", "data": {"endpoint": "some-endpoint"}}}`) 94 }) 95 96 It("sets the health check type to http and has an endpoint", func() { 97 Expect(process).To(Equal(Process{ 98 HealthCheckType: "http", 99 HealthCheckEndpoint: "some-endpoint", 100 })) 101 }) 102 }) 103 104 Context("when health check type port is provided", func() { 105 BeforeEach(func() { 106 processBytes = []byte(`{"health_check":{"type":"port", "data": {"endpoint": null}}}`) 107 }) 108 109 It("sets the health check type to port", func() { 110 Expect(process).To(Equal(Process{ 111 HealthCheckType: "port", 112 })) 113 }) 114 }) 115 116 Context("when health check type process is provided", func() { 117 BeforeEach(func() { 118 processBytes = []byte(`{"health_check":{"type":"process", "data": {"endpoint": null}}}`) 119 }) 120 121 It("sets the health check type to process", func() { 122 Expect(process).To(Equal(Process{ 123 HealthCheckType: "process", 124 })) 125 }) 126 }) 127 }) 128 129 Describe("GetApplicationProcesses", func() { 130 Context("when the application exists", func() { 131 BeforeEach(func() { 132 response1 := fmt.Sprintf(` 133 { 134 "pagination": { 135 "next": { 136 "href": "%s/v3/apps/some-app-guid/processes?page=2" 137 } 138 }, 139 "resources": [ 140 { 141 "guid": "process-1-guid", 142 "type": "web", 143 "memory_in_mb": 32, 144 "health_check": { 145 "type": "port", 146 "data": { 147 "timeout": null, 148 "endpoint": null 149 } 150 } 151 }, 152 { 153 "guid": "process-2-guid", 154 "type": "worker", 155 "memory_in_mb": 64, 156 "health_check": { 157 "type": "http", 158 "data": { 159 "timeout": 60, 160 "endpoint": "/health" 161 } 162 } 163 } 164 ] 165 }`, server.URL()) 166 response2 := ` 167 { 168 "pagination": { 169 "next": null 170 }, 171 "resources": [ 172 { 173 "guid": "process-3-guid", 174 "type": "console", 175 "memory_in_mb": 128, 176 "health_check": { 177 "type": "process", 178 "data": { 179 "timeout": 90, 180 "endpoint": null 181 } 182 } 183 } 184 ] 185 }` 186 server.AppendHandlers( 187 CombineHandlers( 188 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"), 189 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 190 ), 191 ) 192 server.AppendHandlers( 193 CombineHandlers( 194 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"), 195 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 196 ), 197 ) 198 }) 199 200 It("returns a list of processes associated with the application and all warnings", func() { 201 processes, warnings, err := client.GetApplicationProcesses("some-app-guid") 202 Expect(err).ToNot(HaveOccurred()) 203 204 Expect(processes).To(ConsistOf( 205 Process{ 206 GUID: "process-1-guid", 207 Type: constant.ProcessTypeWeb, 208 MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, 209 HealthCheckType: "port", 210 }, 211 Process{ 212 GUID: "process-2-guid", 213 Type: "worker", 214 MemoryInMB: types.NullUint64{Value: 64, IsSet: true}, 215 HealthCheckType: "http", 216 HealthCheckEndpoint: "/health", 217 }, 218 Process{ 219 GUID: "process-3-guid", 220 Type: "console", 221 MemoryInMB: types.NullUint64{Value: 128, IsSet: true}, 222 HealthCheckType: "process", 223 }, 224 )) 225 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 226 }) 227 }) 228 229 Context("when cloud controller returns an error", func() { 230 BeforeEach(func() { 231 response := `{ 232 "errors": [ 233 { 234 "code": 10010, 235 "detail": "App not found", 236 "title": "CF-ResourceNotFound" 237 } 238 ] 239 }` 240 server.AppendHandlers( 241 CombineHandlers( 242 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"), 243 RespondWith(http.StatusNotFound, response), 244 ), 245 ) 246 }) 247 248 It("returns the error", func() { 249 _, _, err := client.GetApplicationProcesses("some-app-guid") 250 Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{})) 251 }) 252 }) 253 }) 254 255 Describe("GetApplicationProcessByType", func() { 256 var ( 257 process Process 258 warnings []string 259 err error 260 ) 261 262 JustBeforeEach(func() { 263 process, warnings, err = client.GetApplicationProcessByType("some-app-guid", "some-type") 264 }) 265 266 Context("when the process exists", func() { 267 BeforeEach(func() { 268 response := `{ 269 "guid": "process-1-guid", 270 "type": "some-type", 271 "memory_in_mb": 32, 272 "health_check": { 273 "type": "http", 274 "data": { 275 "timeout": 90, 276 "endpoint": "/health" 277 } 278 } 279 }` 280 server.AppendHandlers( 281 CombineHandlers( 282 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 283 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 284 ), 285 ) 286 }) 287 288 It("returns the process and all warnings", func() { 289 Expect(err).NotTo(HaveOccurred()) 290 Expect(warnings).To(ConsistOf("this is a warning")) 291 Expect(process).To(Equal(Process{ 292 GUID: "process-1-guid", 293 Type: "some-type", 294 MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, 295 HealthCheckType: "http", 296 HealthCheckEndpoint: "/health", 297 })) 298 }) 299 }) 300 301 Context("when the application does not exist", func() { 302 BeforeEach(func() { 303 response := `{ 304 "errors": [ 305 { 306 "detail": "Application not found", 307 "title": "CF-ResourceNotFound", 308 "code": 10010 309 } 310 ] 311 }` 312 server.AppendHandlers( 313 CombineHandlers( 314 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 315 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 316 ), 317 ) 318 }) 319 320 It("returns a ResourceNotFoundError", func() { 321 Expect(warnings).To(ConsistOf("this is a warning")) 322 Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"})) 323 }) 324 }) 325 326 Context("when the cloud controller returns errors and warnings", func() { 327 BeforeEach(func() { 328 response := `{ 329 "errors": [ 330 { 331 "code": 10008, 332 "detail": "The request is semantically invalid: command presence", 333 "title": "CF-UnprocessableEntity" 334 }, 335 { 336 "code": 10009, 337 "detail": "Some CC Error", 338 "title": "CF-SomeNewError" 339 } 340 ] 341 }` 342 server.AppendHandlers( 343 CombineHandlers( 344 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 345 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 346 ), 347 ) 348 }) 349 350 It("returns the error and all warnings", func() { 351 Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{ 352 ResponseCode: http.StatusTeapot, 353 V3ErrorResponse: ccerror.V3ErrorResponse{ 354 Errors: []ccerror.V3Error{ 355 { 356 Code: 10008, 357 Detail: "The request is semantically invalid: command presence", 358 Title: "CF-UnprocessableEntity", 359 }, 360 { 361 Code: 10009, 362 Detail: "Some CC Error", 363 Title: "CF-SomeNewError", 364 }, 365 }, 366 }, 367 })) 368 Expect(warnings).To(ConsistOf("this is a warning")) 369 }) 370 }) 371 }) 372 373 Describe("PatchApplicationProcessHealthCheck", func() { 374 var ( 375 endpoint string 376 377 process Process 378 warnings []string 379 err error 380 ) 381 382 JustBeforeEach(func() { 383 process, warnings, err = client.PatchApplicationProcessHealthCheck("some-process-guid", "some-type", endpoint) 384 }) 385 386 Context("when patching the process succeeds", func() { 387 Context("and the endpoint is non-empty", func() { 388 BeforeEach(func() { 389 endpoint = "some-endpoint" 390 expectedBody := `{ 391 "health_check": { 392 "type": "some-type", 393 "data": { 394 "endpoint": "some-endpoint" 395 } 396 } 397 }` 398 expectedResponse := `{ 399 "health_check": { 400 "type": "some-type", 401 "data": { 402 "endpoint": "some-endpoint" 403 } 404 } 405 }` 406 server.AppendHandlers( 407 CombineHandlers( 408 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 409 VerifyJSON(expectedBody), 410 RespondWith(http.StatusOK, expectedResponse, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 411 ), 412 ) 413 }) 414 415 It("patches this process's health check", func() { 416 Expect(process).To(Equal(Process{ 417 HealthCheckType: "some-type", 418 HealthCheckEndpoint: "some-endpoint", 419 })) 420 Expect(err).ToNot(HaveOccurred()) 421 Expect(warnings).To(ConsistOf("this is a warning")) 422 }) 423 }) 424 425 Context("and the endpoint is empty", func() { 426 BeforeEach(func() { 427 endpoint = "" 428 expectedBody := `{ 429 "health_check": { 430 "type": "some-type", 431 "data": { 432 "endpoint": null 433 } 434 } 435 }` 436 responseBody := `{ 437 "guid": "some-process-guid", 438 "health_check": { 439 "type": "some-type", 440 "data": { 441 "endpoint": null 442 } 443 } 444 }` 445 server.AppendHandlers( 446 CombineHandlers( 447 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 448 VerifyJSON(expectedBody), 449 RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 450 ), 451 ) 452 }) 453 454 It("patches this process's health check", func() { 455 Expect(process).To(Equal(Process{GUID: "some-process-guid", HealthCheckType: "some-type"})) 456 Expect(err).ToNot(HaveOccurred()) 457 Expect(warnings).To(ConsistOf("this is a warning")) 458 }) 459 }) 460 }) 461 462 Context("when the process does not exist", func() { 463 BeforeEach(func() { 464 endpoint = "some-endpoint" 465 response := `{ 466 "errors": [ 467 { 468 "detail": "Process not found", 469 "title": "CF-ResourceNotFound", 470 "code": 10010 471 } 472 ] 473 }` 474 475 server.AppendHandlers( 476 CombineHandlers( 477 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 478 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 479 ), 480 ) 481 }) 482 483 It("returns an error and warnings", func() { 484 Expect(err).To(MatchError(ccerror.ProcessNotFoundError{})) 485 Expect(warnings).To(ConsistOf("this is a warning")) 486 }) 487 }) 488 489 Context("when the cloud controller returns errors and warnings", func() { 490 BeforeEach(func() { 491 endpoint = "some-endpoint" 492 response := `{ 493 "errors": [ 494 { 495 "code": 10008, 496 "detail": "The request is semantically invalid: command presence", 497 "title": "CF-UnprocessableEntity" 498 }, 499 { 500 "code": 10009, 501 "detail": "Some CC Error", 502 "title": "CF-SomeNewError" 503 } 504 ] 505 }` 506 server.AppendHandlers( 507 CombineHandlers( 508 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 509 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 510 ), 511 ) 512 }) 513 514 It("returns the error and all warnings", func() { 515 Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{ 516 ResponseCode: http.StatusTeapot, 517 V3ErrorResponse: ccerror.V3ErrorResponse{ 518 Errors: []ccerror.V3Error{ 519 { 520 Code: 10008, 521 Detail: "The request is semantically invalid: command presence", 522 Title: "CF-UnprocessableEntity", 523 }, 524 { 525 Code: 10009, 526 Detail: "Some CC Error", 527 Title: "CF-SomeNewError", 528 }, 529 }, 530 }, 531 })) 532 Expect(warnings).To(ConsistOf("this is a warning")) 533 }) 534 }) 535 }) 536 537 Describe("CreateApplicationProcessScale", func() { 538 var passedProcess Process 539 540 Context("when providing all scale options", func() { 541 BeforeEach(func() { 542 passedProcess = Process{ 543 Type: constant.ProcessTypeWeb, 544 Instances: types.NullInt{Value: 2, IsSet: true}, 545 MemoryInMB: types.NullUint64{Value: 100, IsSet: true}, 546 DiskInMB: types.NullUint64{Value: 200, IsSet: true}, 547 } 548 expectedBody := `{ 549 "instances": 2, 550 "memory_in_mb": 100, 551 "disk_in_mb": 200 552 }` 553 response := `{ 554 "guid": "some-process-guid" 555 }` 556 server.AppendHandlers( 557 CombineHandlers( 558 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 559 VerifyJSON(expectedBody), 560 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 561 ), 562 ) 563 }) 564 565 It("scales the application process; returns the scaled process and all warnings", func() { 566 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 567 Expect(process).To(Equal(Process{GUID: "some-process-guid"})) 568 Expect(err).ToNot(HaveOccurred()) 569 Expect(warnings).To(ConsistOf("this is a warning")) 570 }) 571 }) 572 573 Context("when providing all scale options with 0 values", func() { 574 BeforeEach(func() { 575 passedProcess = Process{ 576 Type: constant.ProcessTypeWeb, 577 Instances: types.NullInt{Value: 0, IsSet: true}, 578 MemoryInMB: types.NullUint64{Value: 0, IsSet: true}, 579 DiskInMB: types.NullUint64{Value: 0, IsSet: true}, 580 } 581 expectedBody := `{ 582 "instances": 0, 583 "memory_in_mb": 0, 584 "disk_in_mb": 0 585 }` 586 response := `{ 587 "guid": "some-process-guid" 588 }` 589 server.AppendHandlers( 590 CombineHandlers( 591 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 592 VerifyJSON(expectedBody), 593 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 594 ), 595 ) 596 }) 597 598 It("scales the application process to 0 values; returns the scaled process and all warnings", func() { 599 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 600 Expect(process).To(Equal(Process{GUID: "some-process-guid"})) 601 Expect(err).ToNot(HaveOccurred()) 602 Expect(warnings).To(ConsistOf("this is a warning")) 603 }) 604 }) 605 606 Context("when providing only one scale option", func() { 607 BeforeEach(func() { 608 passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}} 609 expectedBody := `{ 610 "instances": 2 611 }` 612 response := `{ 613 "guid": "some-process-guid", 614 "instances": 2 615 }` 616 server.AppendHandlers( 617 CombineHandlers( 618 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 619 VerifyJSON(expectedBody), 620 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 621 ), 622 ) 623 }) 624 625 It("scales the application process; returns the process object and all warnings", func() { 626 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 627 Expect(process).To(Equal(Process{GUID: "some-process-guid", Instances: types.NullInt{Value: 2, IsSet: true}})) 628 Expect(err).ToNot(HaveOccurred()) 629 Expect(warnings).To(ConsistOf("this is a warning")) 630 }) 631 }) 632 633 Context("when an error is encountered", func() { 634 BeforeEach(func() { 635 passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}} 636 response := `{ 637 "errors": [ 638 { 639 "code": 10008, 640 "detail": "The request is semantically invalid: command presence", 641 "title": "CF-UnprocessableEntity" 642 }, 643 { 644 "code": 10009, 645 "detail": "Some CC Error", 646 "title": "CF-SomeNewError" 647 } 648 ] 649 }` 650 server.AppendHandlers( 651 CombineHandlers( 652 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 653 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 654 ), 655 ) 656 }) 657 658 It("returns an empty process, the error and all warnings", func() { 659 process, warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 660 Expect(process).To(BeZero()) 661 Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{ 662 ResponseCode: http.StatusTeapot, 663 V3ErrorResponse: ccerror.V3ErrorResponse{ 664 Errors: []ccerror.V3Error{ 665 { 666 Code: 10008, 667 Detail: "The request is semantically invalid: command presence", 668 Title: "CF-UnprocessableEntity", 669 }, 670 { 671 Code: 10009, 672 Detail: "Some CC Error", 673 Title: "CF-SomeNewError", 674 }, 675 }, 676 }, 677 })) 678 Expect(warnings).To(ConsistOf("this is a warning")) 679 }) 680 }) 681 }) 682 })