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