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

     1  package api_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"time"
    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  	. "github.com/onsi/ginkgo"
    16  	. "github.com/onsi/gomega"
    17  )
    18  
    19  var _ = Describe("Workers API", func() {
    20  
    21  	Describe("GET /api/v1/workers", func() {
    22  		var response *http.Response
    23  
    24  		JustBeforeEach(func() {
    25  			req, err := http.NewRequest("GET", server.URL+"/api/v1/workers", nil)
    26  			Expect(err).NotTo(HaveOccurred())
    27  
    28  			response, err = client.Do(req)
    29  			Expect(err).NotTo(HaveOccurred())
    30  		})
    31  
    32  		Context("when authenticated", func() {
    33  			var (
    34  				teamWorker1 *dbfakes.FakeWorker
    35  				teamWorker2 *dbfakes.FakeWorker
    36  			)
    37  
    38  			BeforeEach(func() {
    39  				fakeAccess.IsAuthenticatedReturns(true)
    40  				fakeAccess.IsAuthorizedReturns(true)
    41  				fakeAccess.TeamNamesReturns([]string{"some-team"})
    42  				dbWorkerFactory.VisibleWorkersReturns(nil, nil)
    43  
    44  				teamWorker1 = new(dbfakes.FakeWorker)
    45  				gardenAddr1 := "1.2.3.4:7777"
    46  				teamWorker1.GardenAddrReturns(&gardenAddr1)
    47  				bcURL1 := "1.2.3.4:8888"
    48  				teamWorker1.BaggageclaimURLReturns(&bcURL1)
    49  
    50  				teamWorker2 = new(dbfakes.FakeWorker)
    51  				gardenAddr2 := "5.6.7.8:7777"
    52  				teamWorker2.GardenAddrReturns(&gardenAddr2)
    53  				bcURL2 := "5.6.7.8:8888"
    54  				teamWorker2.BaggageclaimURLReturns(&bcURL2)
    55  			})
    56  
    57  			It("fetches workers by team name from worker user context", func() {
    58  				Expect(dbWorkerFactory.VisibleWorkersCallCount()).To(Equal(1))
    59  
    60  				teamNames := dbWorkerFactory.VisibleWorkersArgsForCall(0)
    61  				Expect(teamNames).To(ConsistOf("some-team"))
    62  			})
    63  
    64  			Context("when user is an admin", func() {
    65  				BeforeEach(func() {
    66  					fakeAccess.IsAdminReturns(true)
    67  					dbWorkerFactory.WorkersReturns([]db.Worker{
    68  						teamWorker1,
    69  						teamWorker2,
    70  					}, nil)
    71  				})
    72  
    73  				It("returns all the workers", func() {
    74  					Expect(response.StatusCode).To(Equal(http.StatusOK))
    75  					expectedHeaderEntries := map[string]string{
    76  						"Content-Type": "application/json",
    77  					}
    78  					Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
    79  
    80  					Expect(dbWorkerFactory.WorkersCallCount()).To(Equal(1))
    81  
    82  					var returnedWorkers []atc.Worker
    83  					err := json.NewDecoder(response.Body).Decode(&returnedWorkers)
    84  					Expect(err).NotTo(HaveOccurred())
    85  
    86  					Expect(returnedWorkers).To(Equal([]atc.Worker{
    87  						{
    88  							GardenAddr:      "1.2.3.4:7777",
    89  							BaggageclaimURL: "1.2.3.4:8888",
    90  						},
    91  						{
    92  							GardenAddr:      "5.6.7.8:7777",
    93  							BaggageclaimURL: "5.6.7.8:8888",
    94  						},
    95  					}))
    96  				})
    97  			})
    98  
    99  			Context("when the workers can be listed", func() {
   100  				BeforeEach(func() {
   101  					dbWorkerFactory.VisibleWorkersReturns([]db.Worker{
   102  						teamWorker1,
   103  						teamWorker2,
   104  					}, nil)
   105  				})
   106  
   107  				It("returns 200", func() {
   108  					Expect(response.StatusCode).To(Equal(http.StatusOK))
   109  				})
   110  
   111  				It("returns Content-Type 'application/json'", func() {
   112  					expectedHeaderEntries := map[string]string{
   113  						"Content-Type": "application/json",
   114  					}
   115  					Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   116  				})
   117  
   118  				It("returns the workers", func() {
   119  					var returnedWorkers []atc.Worker
   120  					err := json.NewDecoder(response.Body).Decode(&returnedWorkers)
   121  					Expect(err).NotTo(HaveOccurred())
   122  
   123  					Expect(dbWorkerFactory.VisibleWorkersCallCount()).To(Equal(1))
   124  
   125  					Expect(returnedWorkers).To(Equal([]atc.Worker{
   126  						{
   127  							GardenAddr:      "1.2.3.4:7777",
   128  							BaggageclaimURL: "1.2.3.4:8888",
   129  						},
   130  						{
   131  							GardenAddr:      "5.6.7.8:7777",
   132  							BaggageclaimURL: "5.6.7.8:8888",
   133  						},
   134  					}))
   135  
   136  				})
   137  			})
   138  
   139  			Context("when getting the workers fails", func() {
   140  				BeforeEach(func() {
   141  					dbWorkerFactory.VisibleWorkersReturns(nil, errors.New("error!"))
   142  				})
   143  
   144  				It("returns 500", func() {
   145  					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   146  				})
   147  			})
   148  		})
   149  
   150  		Context("when not authenticated", func() {
   151  			BeforeEach(func() {
   152  				fakeAccess.IsAuthenticatedReturns(false)
   153  			})
   154  
   155  			It("returns 401", func() {
   156  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   157  			})
   158  		})
   159  	})
   160  
   161  	Describe("POST /api/v1/workers", func() {
   162  		var (
   163  			worker    atc.Worker
   164  			ttl       string
   165  			certsPath string
   166  
   167  			response *http.Response
   168  		)
   169  
   170  		BeforeEach(func() {
   171  			certsPath = "/some/certs/path"
   172  			worker = atc.Worker{
   173  				Name:             "worker-name",
   174  				GardenAddr:       "1.2.3.4:7777",
   175  				BaggageclaimURL:  "5.6.7.8:7788",
   176  				CertsPath:        &certsPath,
   177  				HTTPProxyURL:     "http://example.com",
   178  				HTTPSProxyURL:    "https://example.com",
   179  				NoProxy:          "example.com,127.0.0.1,localhost",
   180  				ActiveContainers: 2,
   181  				ActiveVolumes:    10,
   182  				ActiveTasks:      42,
   183  				ResourceTypes: []atc.WorkerResourceType{
   184  					{Type: "some-resource", Image: "some-resource-image"},
   185  				},
   186  				Platform: "haiku",
   187  				Tags:     []string{"not", "a", "limerick"},
   188  				Version:  "1.2.3",
   189  			}
   190  
   191  			ttl = "30s"
   192  			fakeAccess.IsAuthorizedReturns(true)
   193  			fakeAccess.IsSystemReturns(true)
   194  		})
   195  
   196  		JustBeforeEach(func() {
   197  			payload, err := json.Marshal(worker)
   198  			Expect(err).NotTo(HaveOccurred())
   199  
   200  			req, err := http.NewRequest("POST", server.URL+"/api/v1/workers?ttl="+ttl, ioutil.NopCloser(bytes.NewBuffer(payload)))
   201  			Expect(err).NotTo(HaveOccurred())
   202  
   203  			response, err = client.Do(req)
   204  			Expect(err).NotTo(HaveOccurred())
   205  		})
   206  
   207  		Context("when authenticated", func() {
   208  			BeforeEach(func() {
   209  				fakeAccess.IsAuthenticatedReturns(true)
   210  			})
   211  
   212  			It("tries to save the worker", func() {
   213  				Expect(dbWorkerFactory.SaveWorkerCallCount()).To(Equal(1))
   214  				savedWorker, savedTTL := dbWorkerFactory.SaveWorkerArgsForCall(0)
   215  				Expect(savedWorker).To(Equal(atc.Worker{
   216  					GardenAddr:       "1.2.3.4:7777",
   217  					Name:             "worker-name",
   218  					BaggageclaimURL:  "5.6.7.8:7788",
   219  					CertsPath:        &certsPath,
   220  					HTTPProxyURL:     "http://example.com",
   221  					HTTPSProxyURL:    "https://example.com",
   222  					NoProxy:          "example.com,127.0.0.1,localhost",
   223  					ActiveContainers: 2,
   224  					ActiveVolumes:    10,
   225  					ActiveTasks:      42,
   226  					ResourceTypes: []atc.WorkerResourceType{
   227  						{Type: "some-resource", Image: "some-resource-image"},
   228  					},
   229  					Platform: "haiku",
   230  					Tags:     []string{"not", "a", "limerick"},
   231  					Version:  "1.2.3",
   232  				}))
   233  
   234  				Expect(savedTTL.String()).To(Equal(ttl))
   235  			})
   236  
   237  			Context("when request is not from tsa", func() {
   238  				Context("when system claim is false", func() {
   239  					BeforeEach(func() {
   240  						fakeAccess.IsSystemReturns(false)
   241  					})
   242  
   243  					It("return 403", func() {
   244  						Expect(response.StatusCode).To(Equal(http.StatusForbidden))
   245  					})
   246  				})
   247  			})
   248  
   249  			Context("when payload contains team name", func() {
   250  				BeforeEach(func() {
   251  					worker.Team = "some-team"
   252  				})
   253  
   254  				Context("when specified team exists", func() {
   255  					var foundTeam *dbfakes.FakeTeam
   256  
   257  					BeforeEach(func() {
   258  						foundTeam = new(dbfakes.FakeTeam)
   259  						dbWorkerTeamFactory.FindTeamReturns(foundTeam, true, nil)
   260  					})
   261  
   262  					It("saves team name in db", func() {
   263  						Expect(foundTeam.SaveWorkerCallCount()).To(Equal(1))
   264  					})
   265  
   266  					Context("when saving the worker succeeds", func() {
   267  						BeforeEach(func() {
   268  							foundTeam.SaveWorkerReturns(new(dbfakes.FakeWorker), nil)
   269  						})
   270  
   271  						It("returns 200", func() {
   272  							Expect(response.StatusCode).To(Equal(http.StatusOK))
   273  						})
   274  					})
   275  
   276  					Context("when saving the worker fails", func() {
   277  						BeforeEach(func() {
   278  							foundTeam.SaveWorkerReturns(nil, errors.New("oh no!"))
   279  						})
   280  
   281  						It("returns 500", func() {
   282  							Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   283  						})
   284  					})
   285  				})
   286  
   287  				Context("when specified team does not exist", func() {
   288  					BeforeEach(func() {
   289  						dbWorkerTeamFactory.FindTeamReturns(nil, false, nil)
   290  					})
   291  
   292  					It("returns 400", func() {
   293  						Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   294  					})
   295  				})
   296  			})
   297  
   298  			Context("when the worker has no name", func() {
   299  				BeforeEach(func() {
   300  					worker.Name = ""
   301  				})
   302  
   303  				It("tries to save the worker with the garden address as the name", func() {
   304  					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(Equal(1))
   305  
   306  					savedInfo, savedTTL := dbWorkerFactory.SaveWorkerArgsForCall(0)
   307  					Expect(savedInfo).To(Equal(atc.Worker{
   308  						GardenAddr:       "1.2.3.4:7777",
   309  						Name:             "1.2.3.4:7777",
   310  						BaggageclaimURL:  "5.6.7.8:7788",
   311  						CertsPath:        &certsPath,
   312  						HTTPProxyURL:     "http://example.com",
   313  						HTTPSProxyURL:    "https://example.com",
   314  						NoProxy:          "example.com,127.0.0.1,localhost",
   315  						ActiveContainers: 2,
   316  						ActiveVolumes:    10,
   317  						ActiveTasks:      42,
   318  						ResourceTypes: []atc.WorkerResourceType{
   319  							{Type: "some-resource", Image: "some-resource-image"},
   320  						},
   321  						Platform: "haiku",
   322  						Tags:     []string{"not", "a", "limerick"},
   323  						Version:  "1.2.3",
   324  					}))
   325  
   326  					Expect(savedTTL.String()).To(Equal(ttl))
   327  				})
   328  			})
   329  
   330  			Context("when the certs path is null", func() {
   331  				BeforeEach(func() {
   332  					worker.CertsPath = nil
   333  				})
   334  
   335  				It("saves the worker with a null certs path", func() {
   336  					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(Equal(1))
   337  
   338  					savedInfo, savedTTL := dbWorkerFactory.SaveWorkerArgsForCall(0)
   339  					Expect(savedInfo).To(Equal(atc.Worker{
   340  						GardenAddr:       "1.2.3.4:7777",
   341  						Name:             "worker-name",
   342  						BaggageclaimURL:  "5.6.7.8:7788",
   343  						CertsPath:        nil,
   344  						HTTPProxyURL:     "http://example.com",
   345  						HTTPSProxyURL:    "https://example.com",
   346  						NoProxy:          "example.com,127.0.0.1,localhost",
   347  						ActiveContainers: 2,
   348  						ActiveVolumes:    10,
   349  						ActiveTasks:      42,
   350  						ResourceTypes: []atc.WorkerResourceType{
   351  							{Type: "some-resource", Image: "some-resource-image"},
   352  						},
   353  						Platform: "haiku",
   354  						Tags:     []string{"not", "a", "limerick"},
   355  						Version:  "1.2.3",
   356  					}))
   357  
   358  					Expect(savedTTL.String()).To(Equal(ttl))
   359  				})
   360  			})
   361  
   362  			Context("when the certs path is an empty string", func() {
   363  				BeforeEach(func() {
   364  					emptyString := ""
   365  					worker.CertsPath = &emptyString
   366  				})
   367  
   368  				It("saves the worker with a null certs path", func() {
   369  					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(Equal(1))
   370  
   371  					savedInfo, savedTTL := dbWorkerFactory.SaveWorkerArgsForCall(0)
   372  					Expect(savedInfo).To(Equal(atc.Worker{
   373  						GardenAddr:       "1.2.3.4:7777",
   374  						Name:             "worker-name",
   375  						BaggageclaimURL:  "5.6.7.8:7788",
   376  						CertsPath:        nil,
   377  						HTTPProxyURL:     "http://example.com",
   378  						HTTPSProxyURL:    "https://example.com",
   379  						NoProxy:          "example.com,127.0.0.1,localhost",
   380  						ActiveContainers: 2,
   381  						ActiveVolumes:    10,
   382  						ActiveTasks:      42,
   383  						ResourceTypes: []atc.WorkerResourceType{
   384  							{Type: "some-resource", Image: "some-resource-image"},
   385  						},
   386  						Platform: "haiku",
   387  						Tags:     []string{"not", "a", "limerick"},
   388  						Version:  "1.2.3",
   389  					}))
   390  
   391  					Expect(savedTTL.String()).To(Equal(ttl))
   392  				})
   393  			})
   394  
   395  			Context("when saving the worker succeeds", func() {
   396  				var fakeWorker *dbfakes.FakeWorker
   397  				BeforeEach(func() {
   398  					fakeWorker = new(dbfakes.FakeWorker)
   399  					dbWorkerFactory.SaveWorkerReturns(fakeWorker, nil)
   400  				})
   401  
   402  				It("returns 200", func() {
   403  					Expect(response.StatusCode).To(Equal(http.StatusOK))
   404  				})
   405  
   406  			})
   407  
   408  			Context("when saving the worker fails", func() {
   409  				BeforeEach(func() {
   410  					dbWorkerFactory.SaveWorkerReturns(nil, errors.New("oh no!"))
   411  				})
   412  
   413  				It("returns 500", func() {
   414  					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   415  				})
   416  			})
   417  
   418  			Context("when the TTL is invalid", func() {
   419  				BeforeEach(func() {
   420  					ttl = "invalid-duration"
   421  				})
   422  
   423  				It("returns 400", func() {
   424  					Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   425  				})
   426  
   427  				It("returns the validation error in the response body", func() {
   428  					Expect(ioutil.ReadAll(response.Body)).To(Equal([]byte("malformed ttl")))
   429  				})
   430  
   431  				It("does not save it", func() {
   432  					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(BeZero())
   433  				})
   434  			})
   435  
   436  			Context("when the worker has no address", func() {
   437  				BeforeEach(func() {
   438  					worker.GardenAddr = ""
   439  				})
   440  
   441  				It("returns 400", func() {
   442  					Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   443  				})
   444  
   445  				It("returns the validation error in the response body", func() {
   446  					Expect(ioutil.ReadAll(response.Body)).To(Equal([]byte("missing garden address")))
   447  				})
   448  
   449  				It("does not save it", func() {
   450  					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(BeZero())
   451  				})
   452  			})
   453  
   454  			Context("when worker version is invalid", func() {
   455  				BeforeEach(func() {
   456  					worker.Version = "invalid"
   457  				})
   458  
   459  				It("returns 400", func() {
   460  					Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   461  				})
   462  
   463  				It("returns the validation error in the response body", func() {
   464  					Expect(ioutil.ReadAll(response.Body)).To(Equal([]byte("invalid worker version, only numeric characters are allowed")))
   465  				})
   466  
   467  				It("does not save it", func() {
   468  					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(BeZero())
   469  				})
   470  			})
   471  		})
   472  
   473  		Context("when not authenticated", func() {
   474  			BeforeEach(func() {
   475  				fakeAccess.IsAuthenticatedReturns(false)
   476  			})
   477  
   478  			It("returns 401", func() {
   479  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   480  			})
   481  
   482  			It("does not save the config", func() {
   483  				Expect(dbWorkerFactory.SaveWorkerCallCount()).To(BeZero())
   484  			})
   485  		})
   486  	})
   487  
   488  	Describe("PUT /api/v1/workers/:worker_name/land", func() {
   489  		var (
   490  			response   *http.Response
   491  			workerName string
   492  			fakeWorker *dbfakes.FakeWorker
   493  		)
   494  
   495  		JustBeforeEach(func() {
   496  			req, err := http.NewRequest("PUT", server.URL+"/api/v1/workers/"+workerName+"/land", nil)
   497  			Expect(err).NotTo(HaveOccurred())
   498  
   499  			response, err = client.Do(req)
   500  			Expect(err).NotTo(HaveOccurred())
   501  		})
   502  
   503  		BeforeEach(func() {
   504  			fakeWorker = new(dbfakes.FakeWorker)
   505  			workerName = "some-worker"
   506  			fakeWorker.NameReturns(workerName)
   507  			fakeWorker.TeamNameReturns("some-team")
   508  			fakeWorker.LandReturns(nil)
   509  
   510  			fakeAccess.IsAuthenticatedReturns(true)
   511  			dbWorkerFactory.GetWorkerReturns(fakeWorker, true, nil)
   512  		})
   513  
   514  		Context("when the request is authenticated as system", func() {
   515  			BeforeEach(func() {
   516  				fakeAccess.IsSystemReturns(true)
   517  			})
   518  
   519  			It("returns 200", func() {
   520  				Expect(response.StatusCode).To(Equal(http.StatusOK))
   521  			})
   522  
   523  			It("sees if the worker exists and attempts to land it", func() {
   524  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
   525  				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))
   526  				Expect(fakeWorker.LandCallCount()).To(Equal(1))
   527  			})
   528  
   529  			Context("when landing the worker fails", func() {
   530  				var returnedErr error
   531  
   532  				BeforeEach(func() {
   533  					returnedErr = errors.New("some-error")
   534  					fakeWorker.LandReturns(returnedErr)
   535  				})
   536  
   537  				It("returns 500", func() {
   538  					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   539  				})
   540  			})
   541  
   542  			Context("when the worker does not exist", func() {
   543  				BeforeEach(func() {
   544  					dbWorkerFactory.GetWorkerReturns(nil, false, nil)
   545  				})
   546  
   547  				It("returns 404", func() {
   548  					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   549  				})
   550  			})
   551  		})
   552  
   553  		Context("when the request is authorized as the worker's owner", func() {
   554  			BeforeEach(func() {
   555  				fakeAccess.IsAuthorizedReturns(true)
   556  			})
   557  
   558  			It("returns 200", func() {
   559  				Expect(response.StatusCode).To(Equal(http.StatusOK))
   560  			})
   561  		})
   562  
   563  		Context("when the request is authorized as the wrong team", func() {
   564  			BeforeEach(func() {
   565  				fakeAccess.IsAuthorizedReturns(false)
   566  			})
   567  
   568  			It("returns 403", func() {
   569  				Expect(response.StatusCode).To(Equal(http.StatusForbidden))
   570  			})
   571  		})
   572  
   573  		Context("when not authenticated", func() {
   574  			BeforeEach(func() {
   575  				fakeAccess.IsAuthenticatedReturns(false)
   576  			})
   577  
   578  			It("returns 401", func() {
   579  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   580  			})
   581  
   582  			It("does not attempt to find the worker", func() {
   583  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(BeZero())
   584  			})
   585  		})
   586  	})
   587  
   588  	Describe("PUT /api/v1/workers/:worker_name/retire", func() {
   589  		var (
   590  			response   *http.Response
   591  			workerName string
   592  			fakeWorker *dbfakes.FakeWorker
   593  		)
   594  
   595  		JustBeforeEach(func() {
   596  			req, err := http.NewRequest("PUT", server.URL+"/api/v1/workers/"+workerName+"/retire", nil)
   597  			Expect(err).NotTo(HaveOccurred())
   598  
   599  			response, err = client.Do(req)
   600  			Expect(err).NotTo(HaveOccurred())
   601  		})
   602  
   603  		BeforeEach(func() {
   604  			fakeWorker = new(dbfakes.FakeWorker)
   605  			workerName = "some-worker"
   606  			fakeWorker.NameReturns(workerName)
   607  			fakeWorker.TeamNameReturns("some-team")
   608  			fakeAccess.IsAuthenticatedReturns(true)
   609  
   610  			dbWorkerFactory.GetWorkerReturns(fakeWorker, true, nil)
   611  			fakeWorker.RetireReturns(nil)
   612  		})
   613  
   614  		Context("when autheticated as system", func() {
   615  			BeforeEach(func() {
   616  				fakeAccess.IsSystemReturns(true)
   617  			})
   618  
   619  			It("returns 200", func() {
   620  				Expect(response.StatusCode).To(Equal(http.StatusOK))
   621  			})
   622  
   623  			It("sees if the worker exists and attempts to retire it", func() {
   624  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
   625  				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))
   626  
   627  				Expect(fakeWorker.RetireCallCount()).To(Equal(1))
   628  			})
   629  
   630  			Context("when retiring the worker fails", func() {
   631  				var returnedErr error
   632  
   633  				BeforeEach(func() {
   634  					returnedErr = errors.New("some-error")
   635  					fakeWorker.RetireReturns(returnedErr)
   636  				})
   637  
   638  				It("returns 500", func() {
   639  					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   640  				})
   641  			})
   642  
   643  			Context("when the worker does not exist", func() {
   644  				BeforeEach(func() {
   645  					dbWorkerFactory.GetWorkerReturns(nil, false, nil)
   646  				})
   647  
   648  				It("returns 404", func() {
   649  					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   650  				})
   651  			})
   652  		})
   653  
   654  		Context("when authorized as as the worker's owner", func() {
   655  			BeforeEach(func() {
   656  				fakeAccess.IsAuthorizedReturns(true)
   657  			})
   658  
   659  			It("returns 200", func() {
   660  				Expect(response.StatusCode).To(Equal(http.StatusOK))
   661  			})
   662  		})
   663  
   664  		Context("when authorized as some other team", func() {
   665  			BeforeEach(func() {
   666  				fakeAccess.IsAuthorizedReturns(false)
   667  			})
   668  
   669  			It("returns 403", func() {
   670  				Expect(response.StatusCode).To(Equal(http.StatusForbidden))
   671  			})
   672  		})
   673  
   674  		Context("when not authenticated", func() {
   675  			BeforeEach(func() {
   676  				fakeAccess.IsAuthenticatedReturns(false)
   677  			})
   678  
   679  			It("returns 401", func() {
   680  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   681  			})
   682  
   683  			It("does not attempt to find the worker", func() {
   684  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(BeZero())
   685  			})
   686  		})
   687  	})
   688  
   689  	Describe("PUT /api/v1/workers/:worker_name/prune", func() {
   690  		var (
   691  			response   *http.Response
   692  			workerName string
   693  			fakeWorker *dbfakes.FakeWorker
   694  		)
   695  
   696  		JustBeforeEach(func() {
   697  			req, err := http.NewRequest("PUT", server.URL+"/api/v1/workers/"+workerName+"/prune", nil)
   698  			Expect(err).NotTo(HaveOccurred())
   699  
   700  			response, err = client.Do(req)
   701  			Expect(err).NotTo(HaveOccurred())
   702  		})
   703  
   704  		BeforeEach(func() {
   705  			fakeWorker = new(dbfakes.FakeWorker)
   706  			workerName = "some-worker"
   707  			fakeWorker.NameReturns(workerName)
   708  			fakeWorker.TeamNameReturns("some-team")
   709  
   710  			dbWorkerFactory.GetWorkerReturns(fakeWorker, true, nil)
   711  			fakeAccess.IsAuthenticatedReturns(true)
   712  			fakeAccess.IsAuthorizedReturns(true)
   713  			fakeWorker.PruneReturns(nil)
   714  		})
   715  
   716  		It("returns 200", func() {
   717  			Expect(response.StatusCode).To(Equal(http.StatusOK))
   718  		})
   719  
   720  		It("sees if the worker exists and attempts to prune it", func() {
   721  			Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))
   722  			Expect(fakeWorker.PruneCallCount()).To(Equal(1))
   723  		})
   724  
   725  		Context("when pruning the worker fails", func() {
   726  			var returnedErr error
   727  
   728  			BeforeEach(func() {
   729  				returnedErr = errors.New("some-error")
   730  				fakeWorker.PruneReturns(returnedErr)
   731  			})
   732  
   733  			It("returns 500", func() {
   734  				Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   735  			})
   736  		})
   737  
   738  		Context("when the worker does not exist", func() {
   739  			BeforeEach(func() {
   740  				dbWorkerFactory.GetWorkerReturns(nil, false, nil)
   741  			})
   742  
   743  			It("returns 404", func() {
   744  				Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   745  			})
   746  		})
   747  
   748  		Context("when the worker is running", func() {
   749  			BeforeEach(func() {
   750  				fakeWorker.PruneReturns(db.ErrCannotPruneRunningWorker)
   751  			})
   752  
   753  			It("returns 400", func() {
   754  				Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   755  				Expect(ioutil.ReadAll(response.Body)).To(MatchJSON(`{"stderr":"cannot prune running worker"}`))
   756  			})
   757  		})
   758  
   759  		Context("when not authenticated", func() {
   760  			BeforeEach(func() {
   761  				fakeAccess.IsAuthenticatedReturns(false)
   762  			})
   763  
   764  			It("returns 401", func() {
   765  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   766  			})
   767  
   768  			It("does not attempt to find the worker", func() {
   769  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(BeZero())
   770  			})
   771  		})
   772  	})
   773  
   774  	Describe("PUT /api/v1/workers/:worker_name/heartbeat", func() {
   775  		var (
   776  			response   *http.Response
   777  			workerName string
   778  			ttlStr     string
   779  			ttl        time.Duration
   780  			err        error
   781  
   782  			worker     atc.Worker
   783  			fakeWorker *dbfakes.FakeWorker
   784  		)
   785  
   786  		BeforeEach(func() {
   787  			fakeWorker = new(dbfakes.FakeWorker)
   788  			workerName = "some-name"
   789  			fakeWorker.NameReturns(workerName)
   790  			fakeWorker.ActiveContainersReturns(2)
   791  			fakeWorker.ActiveVolumesReturns(10)
   792  			fakeWorker.ActiveTasksReturns(42, nil)
   793  			fakeWorker.PlatformReturns("penguin")
   794  			fakeWorker.TagsReturns([]string{"some-tag"})
   795  			fakeWorker.StateReturns(db.WorkerStateRunning)
   796  			fakeWorker.TeamNameReturns("some-team")
   797  			fakeWorker.EphemeralReturns(true)
   798  
   799  			ttlStr = "30s"
   800  			ttl, err = time.ParseDuration(ttlStr)
   801  			Expect(err).NotTo(HaveOccurred())
   802  
   803  			worker = atc.Worker{
   804  				Name:             workerName,
   805  				ActiveContainers: 2,
   806  			}
   807  			fakeAccess.IsAuthenticatedReturns(true)
   808  			dbWorkerFactory.HeartbeatWorkerReturns(fakeWorker, nil)
   809  		})
   810  
   811  		JustBeforeEach(func() {
   812  			payload, err := json.Marshal(worker)
   813  			Expect(err).NotTo(HaveOccurred())
   814  
   815  			req, err := http.NewRequest("PUT", server.URL+"/api/v1/workers/"+workerName+"/heartbeat?ttl="+ttlStr, ioutil.NopCloser(bytes.NewBuffer(payload)))
   816  			Expect(err).NotTo(HaveOccurred())
   817  
   818  			response, err = client.Do(req)
   819  			Expect(err).NotTo(HaveOccurred())
   820  		})
   821  
   822  		It("returns 200", func() {
   823  			Expect(response.StatusCode).To(Equal(http.StatusOK))
   824  		})
   825  
   826  		It("returns Content-Type 'application/json'", func() {
   827  			expectedHeaderEntries := map[string]string{
   828  				"Content-Type": "application/json",
   829  			}
   830  			Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   831  		})
   832  
   833  		It("returns saved worker", func() {
   834  			contents, err := ioutil.ReadAll(response.Body)
   835  			Expect(err).NotTo(HaveOccurred())
   836  
   837  			Expect(contents).To(MatchJSON(`{
   838  				"name": "some-name",
   839  				"state": "running",
   840  				"addr": "",
   841  				"baggageclaim_url": "",
   842  				"active_containers": 2,
   843  				"active_volumes": 10,
   844  				"active_tasks": 42,
   845  				"resource_types": null,
   846  				"platform": "penguin",
   847  				"ephemeral": true,
   848  				"tags": ["some-tag"],
   849  				"team": "some-team",
   850  				"start_time": 0,
   851  				"version": ""
   852  			}`))
   853  		})
   854  
   855  		It("sees if the worker exists and attempts to heartbeat with provided ttl", func() {
   856  			Expect(dbWorkerFactory.HeartbeatWorkerCallCount()).To(Equal(1))
   857  
   858  			w, t := dbWorkerFactory.HeartbeatWorkerArgsForCall(0)
   859  			Expect(w).To(Equal(worker))
   860  			Expect(t).To(Equal(ttl))
   861  		})
   862  
   863  		Context("when the TTL is invalid", func() {
   864  			BeforeEach(func() {
   865  				ttlStr = "invalid-duration"
   866  			})
   867  
   868  			It("returns 400", func() {
   869  				Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   870  			})
   871  
   872  			It("returns the validation error in the response body", func() {
   873  				Expect(ioutil.ReadAll(response.Body)).To(Equal([]byte("malformed ttl")))
   874  			})
   875  
   876  			It("does not heartbeat worker", func() {
   877  				Expect(dbWorkerFactory.HeartbeatWorkerCallCount()).To(BeZero())
   878  			})
   879  		})
   880  
   881  		Context("when heartbeating the worker fails", func() {
   882  			var returnedErr error
   883  
   884  			BeforeEach(func() {
   885  				returnedErr = errors.New("some-error")
   886  				dbWorkerFactory.HeartbeatWorkerReturns(nil, returnedErr)
   887  			})
   888  
   889  			It("returns 500", func() {
   890  				Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   891  			})
   892  		})
   893  
   894  		Context("when the worker does not exist", func() {
   895  			BeforeEach(func() {
   896  				dbWorkerFactory.HeartbeatWorkerReturns(nil, db.ErrWorkerNotPresent)
   897  			})
   898  
   899  			It("returns 404", func() {
   900  				Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   901  			})
   902  		})
   903  
   904  		Context("when not authenticated", func() {
   905  			BeforeEach(func() {
   906  				fakeAccess.IsAuthenticatedReturns(false)
   907  			})
   908  
   909  			It("returns 401", func() {
   910  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   911  			})
   912  
   913  			It("does not heartbeat the worker", func() {
   914  				Expect(dbWorkerFactory.HeartbeatWorkerCallCount()).To(BeZero())
   915  			})
   916  		})
   917  	})
   918  
   919  	Describe("DELETE /api/v1/workers/:worker_name", func() {
   920  		var (
   921  			response   *http.Response
   922  			workerName string
   923  			fakeWorker *dbfakes.FakeWorker
   924  		)
   925  
   926  		JustBeforeEach(func() {
   927  			req, err := http.NewRequest("DELETE", server.URL+"/api/v1/workers/"+workerName, nil)
   928  			Expect(err).NotTo(HaveOccurred())
   929  
   930  			response, err = client.Do(req)
   931  			Expect(err).NotTo(HaveOccurred())
   932  		})
   933  
   934  		BeforeEach(func() {
   935  			fakeWorker = new(dbfakes.FakeWorker)
   936  			workerName = "some-worker"
   937  			fakeWorker.NameReturns(workerName)
   938  
   939  			fakeAccess.IsAuthenticatedReturns(true)
   940  			fakeWorker.DeleteReturns(nil)
   941  			dbWorkerFactory.GetWorkerReturns(fakeWorker, true, nil)
   942  		})
   943  
   944  		Context("when user is system user", func() {
   945  			BeforeEach(func() {
   946  				fakeAccess.IsSystemReturns(true)
   947  			})
   948  			It("deletes the worker from the DB", func() {
   949  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
   950  				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))
   951  
   952  				Expect(fakeWorker.DeleteCallCount()).To(Equal(1))
   953  			})
   954  			It("returns 200", func() {
   955  				Expect(response.StatusCode).To(Equal(http.StatusOK))
   956  			})
   957  
   958  			Context("when the given worker has already been deleted", func() {
   959  				BeforeEach(func() {
   960  					dbWorkerFactory.GetWorkerReturns(nil, false, nil)
   961  				})
   962  				It("returns 500", func() {
   963  					Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
   964  					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   965  				})
   966  			})
   967  
   968  			Context("when deleting the worker fails", func() {
   969  				var returnedErr error
   970  
   971  				BeforeEach(func() {
   972  					returnedErr = errors.New("some-error")
   973  					fakeWorker.DeleteReturns(returnedErr)
   974  				})
   975  
   976  				It("returns 500", func() {
   977  					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   978  				})
   979  			})
   980  		})
   981  
   982  		Context("when user is admin user", func() {
   983  			BeforeEach(func() {
   984  				fakeAccess.IsAdminReturns(true)
   985  			})
   986  			It("deletes the worker from the DB", func() {
   987  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
   988  				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))
   989  
   990  				Expect(fakeWorker.DeleteCallCount()).To(Equal(1))
   991  			})
   992  			It("returns 200", func() {
   993  				Expect(response.StatusCode).To(Equal(http.StatusOK))
   994  			})
   995  		})
   996  
   997  		Context("when user is authorized for team", func() {
   998  			BeforeEach(func() {
   999  				fakeWorker.TeamNameReturns("some-team")
  1000  				fakeAccess.IsAuthorizedReturns(true)
  1001  			})
  1002  			It("deletes the worker from the DB", func() {
  1003  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
  1004  				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))
  1005  
  1006  				Expect(fakeWorker.DeleteCallCount()).To(Equal(1))
  1007  			})
  1008  			It("returns 200", func() {
  1009  				Expect(response.StatusCode).To(Equal(http.StatusOK))
  1010  			})
  1011  		})
  1012  
  1013  		Context("when not authenticated", func() {
  1014  			BeforeEach(func() {
  1015  				fakeAccess.IsAuthenticatedReturns(false)
  1016  			})
  1017  
  1018  			It("returns 401", func() {
  1019  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
  1020  			})
  1021  
  1022  			It("does not attempt to find the worker", func() {
  1023  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(BeZero())
  1024  			})
  1025  		})
  1026  	})
  1027  })