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