github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/api/volumes_test.go (about)

     1  package api_test
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/url"
    10  
    11  	"github.com/pf-qiu/concourse/v6/atc"
    12  	"github.com/pf-qiu/concourse/v6/atc/db"
    13  	"github.com/pf-qiu/concourse/v6/atc/db/dbfakes"
    14  	. "github.com/pf-qiu/concourse/v6/atc/testhelpers"
    15  
    16  	. "github.com/onsi/ginkgo"
    17  	. "github.com/onsi/gomega"
    18  )
    19  
    20  var _ = Describe("Volumes API", func() {
    21  
    22  	var fakeWorker *dbfakes.FakeWorker
    23  
    24  	BeforeEach(func() {
    25  		fakeWorker = new(dbfakes.FakeWorker)
    26  		fakeWorker.NameReturns("some-worker")
    27  	})
    28  
    29  	Describe("GET /api/v1//teams/a-team/volumes", func() {
    30  		var response *http.Response
    31  
    32  		JustBeforeEach(func() {
    33  			var err error
    34  			response, err = client.Get(server.URL + "/api/v1/teams/a-team/volumes")
    35  			Expect(err).NotTo(HaveOccurred())
    36  		})
    37  
    38  		Context("when not authenticated", func() {
    39  			BeforeEach(func() {
    40  				fakeAccess.IsAuthenticatedReturns(false)
    41  			})
    42  
    43  			It("returns 401 Unauthorized", func() {
    44  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
    45  			})
    46  		})
    47  
    48  		Context("when authenticated", func() {
    49  			BeforeEach(func() {
    50  				fakeAccess.IsAuthenticatedReturns(true)
    51  				fakeAccess.IsAuthorizedReturns(true)
    52  			})
    53  
    54  			Context("when identifying the team succeeds", func() {
    55  				BeforeEach(func() {
    56  					dbTeam.IDReturns(1)
    57  				})
    58  
    59  				It("receives correct team name as function argument", func() {
    60  					Expect(fakeAccess.IsAuthorizedArgsForCall(0)).To(Equal("a-team"))
    61  				})
    62  
    63  				It("asks the factory for the volumes", func() {
    64  					Expect(fakeVolumeRepository.GetTeamVolumesCallCount()).To(Equal(1))
    65  				})
    66  
    67  				Context("when getting all volumes succeeds", func() {
    68  					BeforeEach(func() {
    69  						someOtherFakeWorker := new(dbfakes.FakeWorker)
    70  						someOtherFakeWorker.NameReturns("some-other-worker")
    71  
    72  						fakeVolumeRepository.GetTeamVolumesStub = func(teamID int) ([]db.CreatedVolume, error) {
    73  							if teamID != 1 {
    74  								return []db.CreatedVolume{}, nil
    75  							}
    76  
    77  							volume1 := new(dbfakes.FakeCreatedVolume)
    78  							volume1.HandleReturns("some-resource-cache-handle")
    79  							volume1.WorkerNameReturns(fakeWorker.Name())
    80  							volume1.TypeReturns(db.VolumeTypeResource)
    81  							volume1.ResourceTypeReturns(&db.VolumeResourceType{
    82  								ResourceType: &db.VolumeResourceType{
    83  									WorkerBaseResourceType: &db.UsedWorkerBaseResourceType{
    84  										Name:    "some-base-resource-type",
    85  										Version: "some-base-version",
    86  									},
    87  									Version: atc.Version{"custom": "version"},
    88  								},
    89  								Version: atc.Version{"some": "version"},
    90  							}, nil)
    91  							volume2 := new(dbfakes.FakeCreatedVolume)
    92  							volume2.HandleReturns("some-import-handle")
    93  							volume2.WorkerNameReturns(fakeWorker.Name())
    94  							volume2.TypeReturns(db.VolumeTypeResourceType)
    95  							volume2.BaseResourceTypeReturns(&db.UsedWorkerBaseResourceType{
    96  								Name:    "some-base-resource-type",
    97  								Version: "some-base-version",
    98  							}, nil)
    99  							volume3 := new(dbfakes.FakeCreatedVolume)
   100  							volume3.HandleReturns("some-output-handle")
   101  							volume3.WorkerNameReturns(someOtherFakeWorker.Name())
   102  							volume3.ContainerHandleReturns("some-container-handle")
   103  							volume3.PathReturns("some-path")
   104  							volume3.ParentHandleReturns("some-parent-handle")
   105  							volume3.TypeReturns(db.VolumeTypeContainer)
   106  							volume4 := new(dbfakes.FakeCreatedVolume)
   107  							volume4.HandleReturns("some-cow-handle")
   108  							volume4.WorkerNameReturns(fakeWorker.Name())
   109  							volume4.ContainerHandleReturns("some-container-handle")
   110  							volume4.PathReturns("some-path")
   111  							volume4.TypeReturns(db.VolumeTypeContainer)
   112  							volume5 := new(dbfakes.FakeCreatedVolume)
   113  							volume5.HandleReturns("some-task-cache-handle")
   114  							volume5.WorkerNameReturns(fakeWorker.Name())
   115  							volume5.TypeReturns(db.VolumeTypeTaskCache)
   116  							volume5.TaskIdentifierReturns(1, atc.PipelineRef{Name: "some-pipeline", InstanceVars: atc.InstanceVars{"branch": "master"}}, "some-job", "some-task", nil)
   117  							return []db.CreatedVolume{
   118  								volume1,
   119  								volume2,
   120  								volume3,
   121  								volume4,
   122  								volume5,
   123  							}, nil
   124  						}
   125  					})
   126  
   127  					It("returns 200 OK", func() {
   128  						Expect(response.StatusCode).To(Equal(http.StatusOK))
   129  					})
   130  
   131  					It("returns Content-Type 'application/json'", func() {
   132  						expectedHeaderEntries := map[string]string{
   133  							"Content-Type": "application/json",
   134  						}
   135  						Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   136  					})
   137  
   138  					It("returns all volumes", func() {
   139  						body, err := ioutil.ReadAll(response.Body)
   140  						Expect(err).NotTo(HaveOccurred())
   141  
   142  						Expect(body).To(MatchJSON(`[
   143  		 					{
   144  		 						"id": "some-resource-cache-handle",
   145  		 						"worker_name": "some-worker",
   146  		 						"type": "resource",
   147  		 						"container_handle": "",
   148  		 						"path": "",
   149  		 						"parent_handle": "",
   150  		 						"resource_type": {
   151  		 							"resource_type": {
   152  		 							  "resource_type": null,
   153  		 								"base_resource_type": {
   154  		 									"name": "some-base-resource-type",
   155  		 									"version": "some-base-version"
   156  		 								},
   157  		 								"version": {"custom": "version"}
   158  		 							},
   159  		 							"base_resource_type": null,
   160  		 							"version": {"some": "version"}
   161  		 						},
   162  		 						"base_resource_type": null,
   163  		 						"pipeline_id": 0,
   164  		 						"pipeline_name": "",
   165  		 						"pipeline_instance_vars": null,
   166  		 						"job_name": "",
   167  		 						"step_name": ""
   168  		 					},
   169  		 					{
   170  		 						"id": "some-import-handle",
   171  		 						"worker_name": "some-worker",
   172  		 						"type": "resource-type",
   173  		 						"container_handle": "",
   174  		 						"path": "",
   175  		 						"parent_handle": "",
   176  		 						"resource_type": null,
   177  		 						"base_resource_type": {
   178  		 							"name": "some-base-resource-type",
   179  		 							"version": "some-base-version"
   180  		 						},
   181  		 						"pipeline_id": 0,
   182  		 						"pipeline_name": "",
   183  		 						"pipeline_instance_vars": null,
   184  		 						"job_name": "",
   185  		 						"step_name": ""
   186  		 					},
   187  		 					{
   188  		 						"id": "some-output-handle",
   189  		 						"worker_name": "some-other-worker",
   190  		 						"type": "container",
   191  		 						"container_handle": "some-container-handle",
   192  		 						"path": "some-path",
   193  		 						"parent_handle": "some-parent-handle",
   194  		 						"resource_type": null,
   195  		 						"base_resource_type": null,
   196  		 						"pipeline_id": 0,
   197  		 						"pipeline_name": "",
   198  		 						"pipeline_instance_vars": null,
   199  		 						"job_name": "",
   200  		 						"step_name": ""
   201  		 					},
   202  		 					{
   203  		 						"id": "some-cow-handle",
   204  		 						"worker_name": "some-worker",
   205  		 						"type": "container",
   206  		 						"container_handle": "some-container-handle",
   207  		 						"parent_handle": "",
   208  		 						"path": "some-path",
   209  		 						"resource_type": null,
   210  		 						"base_resource_type": null,
   211  		 						"pipeline_id": 0,
   212  		 						"pipeline_name": "",
   213  		 						"pipeline_instance_vars": null,
   214  		 						"job_name": "",
   215  		 						"step_name": ""
   216  		 					},
   217  		 					{
   218  		 						"id": "some-task-cache-handle",
   219  		 						"worker_name": "some-worker",
   220  		 						"type": "task-cache",
   221  		 						"container_handle": "",
   222  		 						"parent_handle": "",
   223  		 						"path": "",
   224  		 						"resource_type": null,
   225  		 						"base_resource_type": null,
   226  		 						"pipeline_id": 1,
   227  		 						"pipeline_name": "some-pipeline",
   228  		 						"pipeline_instance_vars": {
   229  									"branch": "master"
   230  								},
   231  		 						"job_name": "some-job",
   232  		 						"step_name": "some-task"
   233  		 					}
   234  		 				]`,
   235  						))
   236  					})
   237  				})
   238  
   239  				Context("when getting all volumes fails", func() {
   240  					BeforeEach(func() {
   241  						fakeVolumeRepository.GetTeamVolumesReturns([]db.CreatedVolume{}, errors.New("oh no!"))
   242  					})
   243  
   244  					It("returns 500 Internal Server Error", func() {
   245  						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   246  					})
   247  				})
   248  
   249  				Context("when a volume is deleted during the request", func() {
   250  					BeforeEach(func() {
   251  						fakeVolumeRepository.GetTeamVolumesStub = func(teamID int) ([]db.CreatedVolume, error) {
   252  							volume1 := new(dbfakes.FakeCreatedVolume)
   253  							volume1.ResourceTypeReturns(nil, errors.New("Something"))
   254  
   255  							volume2 := new(dbfakes.FakeCreatedVolume)
   256  							volume2.HandleReturns("some-import-handle")
   257  							volume2.WorkerNameReturns(fakeWorker.Name())
   258  							volume2.TypeReturns(db.VolumeTypeResourceType)
   259  							volume2.BaseResourceTypeReturns(&db.UsedWorkerBaseResourceType{
   260  								Name:    "some-base-resource-type",
   261  								Version: "some-base-version",
   262  							}, nil)
   263  							return []db.CreatedVolume{
   264  								volume1,
   265  								volume2,
   266  							}, nil
   267  						}
   268  					})
   269  
   270  					It("returns a partial list of volumes", func() {
   271  						body, err := ioutil.ReadAll(response.Body)
   272  						Expect(err).NotTo(HaveOccurred())
   273  
   274  						Expect(body).To(MatchJSON(`[
   275  		 					{
   276  		 						"id": "some-import-handle",
   277  		 						"worker_name": "some-worker",
   278  		 						"type": "resource-type",
   279  		 						"container_handle": "",
   280  		 						"path": "",
   281  		 						"parent_handle": "",
   282  		 						"resource_type": null,
   283  		 						"base_resource_type": {
   284  		 							"name": "some-base-resource-type",
   285  		 							"version": "some-base-version"
   286  		 						},
   287  		 						"pipeline_id": 0,
   288  		 						"pipeline_name": "",
   289  		 						"pipeline_instance_vars": null,
   290  		 						"job_name": "",
   291  		 						"step_name": ""
   292  		 					}]`))
   293  					})
   294  
   295  					It("returns 200 OK", func() {
   296  						Expect(response.StatusCode).To(Equal(http.StatusOK))
   297  					})
   298  
   299  					It("returns Content-Type 'application/json'", func() {
   300  						expectedHeaderEntries := map[string]string{
   301  							"Content-Type": "application/json",
   302  						}
   303  						Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   304  					})
   305  				})
   306  			})
   307  		})
   308  	})
   309  
   310  	Describe("GET /api/v1/volumes/destroying", func() {
   311  		var response *http.Response
   312  		var req *http.Request
   313  
   314  		BeforeEach(func() {
   315  			var err error
   316  			req, err = http.NewRequest("GET", server.URL+"/api/v1/volumes/destroying", nil)
   317  			Expect(err).NotTo(HaveOccurred())
   318  		})
   319  
   320  		JustBeforeEach(func() {
   321  			var err error
   322  			response, err = client.Do(req)
   323  			Expect(err).NotTo(HaveOccurred())
   324  		})
   325  
   326  		Context("when not authenticated", func() {
   327  			BeforeEach(func() {
   328  				fakeAccess.IsAuthenticatedReturns(false)
   329  			})
   330  
   331  			It("returns 401 Unauthorized", func() {
   332  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   333  			})
   334  		})
   335  
   336  		Context("when authenticated", func() {
   337  			BeforeEach(func() {
   338  				fakeAccess.IsAuthenticatedReturns(true)
   339  				fakeAccess.IsSystemReturns(true)
   340  			})
   341  
   342  			Context("when worker name is in params", func() {
   343  				BeforeEach(func() {
   344  					req.URL.RawQuery = url.Values{
   345  						"worker_name": []string{"some-worker-name"},
   346  					}.Encode()
   347  				})
   348  
   349  				It("asks the factory for the detroying volumes", func() {
   350  					Expect(fakeDestroyer.FindDestroyingVolumesForGcCallCount()).To(Equal(1))
   351  					Expect(fakeDestroyer.FindDestroyingVolumesForGcArgsForCall(0)).To(Equal("some-worker-name"))
   352  				})
   353  
   354  				Context("when getting all destroying volumes succeeds", func() {
   355  					BeforeEach(func() {
   356  						fakeDestroyer.FindDestroyingVolumesForGcReturns([]string{
   357  							"volume1",
   358  							"volume2",
   359  						}, nil)
   360  					})
   361  
   362  					It("returns 200 OK", func() {
   363  						Expect(response.StatusCode).To(Equal(http.StatusOK))
   364  					})
   365  
   366  					It("returns all volumes", func() {
   367  						body, err := ioutil.ReadAll(response.Body)
   368  						Expect(err).NotTo(HaveOccurred())
   369  
   370  						Expect(body).To(MatchJSON(`[
   371  								"volume1",
   372  								"volume2"
   373  						]`,
   374  						))
   375  					})
   376  
   377  					Context("when getting all volumes fails", func() {
   378  						BeforeEach(func() {
   379  							fakeDestroyer.FindDestroyingVolumesForGcReturns([]string{}, errors.New("oh no!"))
   380  						})
   381  
   382  						It("returns 500 Internal Server Error", func() {
   383  							Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   384  						})
   385  					})
   386  
   387  					Context("when list of volume is empty", func() {
   388  						BeforeEach(func() {
   389  							fakeDestroyer.FindDestroyingVolumesForGcReturns([]string{}, nil)
   390  						})
   391  
   392  						It("returns empty list of volumes", func() {
   393  							body, err := ioutil.ReadAll(response.Body)
   394  							Expect(err).NotTo(HaveOccurred())
   395  
   396  							Expect(body).To(MatchJSON(`[]`))
   397  						})
   398  						It("returns 200 OK", func() {
   399  							Expect(response.StatusCode).To(Equal(http.StatusOK))
   400  						})
   401  					})
   402  				})
   403  			})
   404  
   405  			Context("when worker name is not in params", func() {
   406  				It("returns 400 Bad Request", func() {
   407  					Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   408  				})
   409  			})
   410  		})
   411  	})
   412  
   413  	Describe("PUT /api/v1/volumes/report", func() {
   414  		var response *http.Response
   415  		var req *http.Request
   416  		var body io.Reader
   417  		var err error
   418  
   419  		BeforeEach(func() {
   420  			body = bytes.NewBufferString(`
   421  				[
   422  					"handle1",
   423  					"handle2"
   424  				]
   425  			`)
   426  		})
   427  		JustBeforeEach(func() {
   428  			req, err = http.NewRequest("PUT", server.URL+"/api/v1/volumes/report", body)
   429  			Expect(err).NotTo(HaveOccurred())
   430  			req.Header.Set("Content-Type", "application/json")
   431  		})
   432  
   433  		Context("when not authenticated", func() {
   434  			BeforeEach(func() {
   435  				fakeAccess.IsAuthenticatedReturns(false)
   436  			})
   437  
   438  			It("returns 401 Unauthorized", func() {
   439  				response, err = client.Do(req)
   440  				Expect(err).NotTo(HaveOccurred())
   441  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   442  			})
   443  		})
   444  
   445  		Context("when authenticated as system", func() {
   446  			BeforeEach(func() {
   447  				fakeAccess.IsAuthenticatedReturns(true)
   448  				fakeAccess.IsSystemReturns(true)
   449  			})
   450  
   451  			Context("with no params", func() {
   452  				It("returns 404", func() {
   453  					response, err = client.Do(req)
   454  					Expect(err).NotTo(HaveOccurred())
   455  					Expect(fakeDestroyer.DestroyVolumesCallCount()).To(Equal(0))
   456  					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   457  				})
   458  
   459  				It("returns Content-Type application/json", func() {
   460  					response, err = client.Do(req)
   461  					Expect(err).NotTo(HaveOccurred())
   462  					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   463  					expectedHeaderEntries := map[string]string{
   464  						"Content-Type": "application/json",
   465  					}
   466  					Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   467  				})
   468  			})
   469  
   470  			Context("querying with worker name", func() {
   471  				JustBeforeEach(func() {
   472  					req.URL.RawQuery = url.Values{
   473  						"worker_name": []string{"some-worker-name"},
   474  					}.Encode()
   475  				})
   476  
   477  				Context("with invalid json", func() {
   478  					BeforeEach(func() {
   479  						body = bytes.NewBufferString(`{}`)
   480  					})
   481  
   482  					It("returns 400", func() {
   483  						response, err = client.Do(req)
   484  						Expect(err).NotTo(HaveOccurred())
   485  						Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   486  					})
   487  				})
   488  
   489  				Context("when there is an error", func() {
   490  					BeforeEach(func() {
   491  						fakeDestroyer.DestroyVolumesReturns(errors.New("some error"))
   492  					})
   493  
   494  					It("returns 500", func() {
   495  						response, err = client.Do(req)
   496  						Expect(err).NotTo(HaveOccurred())
   497  						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   498  					})
   499  				})
   500  
   501  				Context("when volumes are destroyed", func() {
   502  					BeforeEach(func() {
   503  						fakeDestroyer.DestroyVolumesReturns(nil)
   504  					})
   505  
   506  					It("returns 204", func() {
   507  						response, err = client.Do(req)
   508  						Expect(err).NotTo(HaveOccurred())
   509  						Expect(response.StatusCode).To(Equal(http.StatusNoContent))
   510  					})
   511  				})
   512  
   513  				It("queries with it in the worker name", func() {
   514  					_, err = client.Do(req)
   515  					Expect(err).NotTo(HaveOccurred())
   516  					Expect(fakeDestroyer.DestroyVolumesCallCount()).To(Equal(1))
   517  
   518  					workerName, handles := fakeDestroyer.DestroyVolumesArgsForCall(0)
   519  					Expect(workerName).To(Equal("some-worker-name"))
   520  					Expect(handles).To(Equal([]string{"handle1", "handle2"}))
   521  				})
   522  
   523  				It("marks volumes as missing", func() {
   524  					_, err = client.Do(req)
   525  					Expect(err).NotTo(HaveOccurred())
   526  					Expect(fakeVolumeRepository.UpdateVolumesMissingSinceCallCount()).To(Equal(1))
   527  
   528  					workerName, handles := fakeVolumeRepository.UpdateVolumesMissingSinceArgsForCall(0)
   529  					Expect(workerName).To(Equal("some-worker-name"))
   530  					Expect(handles).To(Equal([]string{"handle1", "handle2"}))
   531  				})
   532  			})
   533  		})
   534  	})
   535  })