github.com/chenbh/concourse/v6@v6.4.2/worker/beacon_test.go (about) 1 package worker_test 2 3 import ( 4 "context" 5 "errors" 6 "os" 7 "syscall" 8 "time" 9 10 "code.cloudfoundry.org/lager" 11 "github.com/chenbh/concourse/v6/tsa" 12 "github.com/chenbh/concourse/v6/worker" 13 "github.com/chenbh/concourse/v6/worker/workerfakes" 14 . "github.com/onsi/ginkgo" 15 . "github.com/onsi/gomega" 16 "github.com/tedsuo/ifrit" 17 ) 18 19 var _ = Describe("Beacon", func() { 20 var ( 21 beacon *worker.Beacon 22 fakeClient *workerfakes.FakeTSAClient 23 24 drainSignals chan<- os.Signal 25 26 process ifrit.Process 27 ) 28 29 BeforeEach(func() { 30 fakeClient = new(workerfakes.FakeTSAClient) 31 32 logger := lager.NewLogger("test") 33 logger.RegisterSink(lager.NewPrettySink(GinkgoWriter, lager.DEBUG)) 34 35 ds := make(chan os.Signal, 1) 36 drainSignals = ds 37 38 beacon = &worker.Beacon{ 39 Logger: logger, 40 41 Client: fakeClient, 42 43 DrainSignals: ds, 44 45 LocalGardenNetwork: "some-garden-network", 46 LocalGardenAddr: "some-garden-addr", 47 48 LocalBaggageclaimNetwork: "some-baggageclaim-network", 49 LocalBaggageclaimAddr: "some-baggageclaim-addr", 50 } 51 }) 52 53 JustBeforeEach(func() { 54 process = ifrit.Background(beacon) 55 }) 56 57 It("registers with the configured garden/baggageclaim info", func() { 58 Eventually(fakeClient.RegisterCallCount).Should(Equal(1)) 59 _, opts := fakeClient.RegisterArgsForCall(0) 60 Expect(opts.LocalGardenNetwork).To(Equal(beacon.LocalGardenNetwork)) 61 Expect(opts.LocalGardenAddr).To(Equal(beacon.LocalGardenAddr)) 62 Expect(opts.LocalBaggageclaimNetwork).To(Equal(beacon.LocalBaggageclaimNetwork)) 63 Expect(opts.LocalBaggageclaimAddr).To(Equal(beacon.LocalBaggageclaimAddr)) 64 }) 65 66 Context("during registration", func() { 67 BeforeEach(func() { 68 fakeClient.RegisterStub = func(ctx context.Context, opts tsa.RegisterOptions) error { 69 opts.RegisteredFunc() 70 71 <-ctx.Done() 72 73 return nil 74 } 75 }) 76 77 Describe("sending a signal", func() { 78 It("cancels the registration context", func() { 79 Eventually(fakeClient.RegisterCallCount).Should(Equal(1)) 80 ctx, _ := fakeClient.RegisterArgsForCall(0) 81 82 Consistently(ctx.Done()).ShouldNot(BeClosed()) 83 84 process.Signal(os.Interrupt) 85 86 Eventually(ctx.Done()).Should(BeClosed()) 87 }) 88 }) 89 90 Describe("Drained", func() { 91 It("returns false", func() { 92 Expect(beacon.Drained()).Should(BeFalse()) 93 }) 94 }) 95 96 Context("when syscall.SIGUSR1 is received", func() { 97 JustBeforeEach(func() { 98 drainSignals <- syscall.SIGUSR1 99 }) 100 101 It("lands the worker without exiting", func() { 102 Eventually(fakeClient.LandCallCount).Should(Equal(1)) 103 Consistently(process.Wait()).ShouldNot(Receive()) 104 }) 105 106 Describe("Drained", func() { 107 It("returns true", func() { 108 Eventually(beacon.Drained).Should(BeTrue()) 109 }) 110 }) 111 112 Context("when landing the worker fails", func() { 113 BeforeEach(func() { 114 fakeClient.LandReturns(errors.New("nope")) 115 }) 116 117 It("exits with the error", func() { 118 Expect(<-process.Wait()).To(MatchError("nope")) 119 }) 120 }) 121 122 Context("when syscall.SIGTERM is received after landing", func() { 123 JustBeforeEach(func() { 124 Eventually(fakeClient.LandCallCount).Should(Equal(1)) 125 process.Signal(syscall.SIGTERM) 126 }) 127 128 It("exits without deleting the worker", func() { 129 Expect(<-process.Wait()).ToNot(HaveOccurred()) 130 Expect(fakeClient.DeleteCallCount()).Should(Equal(0)) 131 }) 132 133 Describe("Drained", func() { 134 It("still returns true", func() { 135 Consistently(beacon.Drained).Should(BeTrue()) 136 }) 137 }) 138 }) 139 140 Context("when syscall.SIGINT is received after landing", func() { 141 JustBeforeEach(func() { 142 Eventually(fakeClient.LandCallCount).Should(Equal(1)) 143 process.Signal(syscall.SIGINT) 144 }) 145 146 It("exits without deleting the worker", func() { 147 Expect(<-process.Wait()).ToNot(HaveOccurred()) 148 Expect(fakeClient.DeleteCallCount()).Should(Equal(0)) 149 }) 150 151 Describe("Drained", func() { 152 It("still returns true", func() { 153 Consistently(beacon.Drained).Should(BeTrue()) 154 }) 155 }) 156 }) 157 }) 158 159 Context("when syscall.SIGUSR2 is received", func() { 160 JustBeforeEach(func() { 161 drainSignals <- syscall.SIGUSR2 162 }) 163 164 It("retires the worker without exiting", func() { 165 Eventually(fakeClient.RetireCallCount).Should(Equal(1)) 166 Consistently(process.Wait()).ShouldNot(Receive()) 167 }) 168 169 Describe("Drained", func() { 170 It("returns true", func() { 171 Eventually(beacon.Drained).Should(BeTrue()) 172 }) 173 }) 174 175 Context("when retiring the worker fails", func() { 176 BeforeEach(func() { 177 fakeClient.RetireReturns(errors.New("nope")) 178 }) 179 180 It("exits with the error", func() { 181 Expect(<-process.Wait()).To(MatchError("nope")) 182 }) 183 }) 184 185 Context("when syscall.SIGTERM is received after retiring", func() { 186 JustBeforeEach(func() { 187 Eventually(fakeClient.RetireCallCount).Should(Equal(1)) 188 process.Signal(syscall.SIGTERM) 189 }) 190 191 It("deletes the worker and exits", func() { 192 Eventually(fakeClient.DeleteCallCount).Should(Equal(1)) 193 Expect(<-process.Wait()).ToNot(HaveOccurred()) 194 }) 195 196 Describe("Drained", func() { 197 It("still returns true", func() { 198 Consistently(beacon.Drained).Should(BeTrue()) 199 }) 200 }) 201 202 Context("when deleting the worker fails", func() { 203 BeforeEach(func() { 204 fakeClient.DeleteReturns(errors.New("nope")) 205 }) 206 207 It("exits with the error", func() { 208 Expect(<-process.Wait()).To(MatchError("nope")) 209 }) 210 }) 211 }) 212 213 Context("when syscall.SIGINT is received after retiring", func() { 214 JustBeforeEach(func() { 215 Eventually(fakeClient.RetireCallCount).Should(Equal(1)) 216 process.Signal(syscall.SIGINT) 217 }) 218 219 It("deletes the worker and exits", func() { 220 Eventually(fakeClient.DeleteCallCount).Should(Equal(1)) 221 Expect(<-process.Wait()).ToNot(HaveOccurred()) 222 }) 223 224 Describe("Drained", func() { 225 It("still returns true", func() { 226 Consistently(beacon.Drained).Should(BeTrue()) 227 }) 228 }) 229 230 Context("when deleting the worker fails", func() { 231 BeforeEach(func() { 232 fakeClient.DeleteReturns(errors.New("nope")) 233 }) 234 235 It("exits with the error", func() { 236 Expect(<-process.Wait()).To(MatchError("nope")) 237 }) 238 }) 239 }) 240 }) 241 242 Context("when syscall.SIGTERM is received", func() { 243 JustBeforeEach(func() { 244 process.Signal(syscall.SIGTERM) 245 }) 246 247 It("exits without landing, retiring, or deleting the worker", func() { 248 Expect(<-process.Wait()).ToNot(HaveOccurred()) 249 Expect(fakeClient.LandCallCount()).Should(Equal(0)) 250 Expect(fakeClient.RetireCallCount()).Should(Equal(0)) 251 Expect(fakeClient.DeleteCallCount()).Should(Equal(0)) 252 }) 253 254 Describe("Drained", func() { 255 It("returns false", func() { 256 Consistently(beacon.Drained).Should(BeFalse()) 257 }) 258 }) 259 }) 260 261 Context("when syscall.SIGINT is received", func() { 262 JustBeforeEach(func() { 263 process.Signal(syscall.SIGINT) 264 }) 265 266 It("exits without landing, retiring, or deleting the worker", func() { 267 Expect(<-process.Wait()).ToNot(HaveOccurred()) 268 Expect(fakeClient.LandCallCount()).Should(Equal(0)) 269 Expect(fakeClient.RetireCallCount()).Should(Equal(0)) 270 Expect(fakeClient.DeleteCallCount()).Should(Equal(0)) 271 }) 272 273 Describe("Drained", func() { 274 It("returns false", func() { 275 Consistently(beacon.Drained).Should(BeFalse()) 276 }) 277 }) 278 }) 279 }) 280 281 Context("when a connection drain timeout is configured", func() { 282 BeforeEach(func() { 283 beacon.ConnectionDrainTimeout = time.Hour 284 }) 285 286 It("configures it in the register options", func() { 287 Eventually(fakeClient.RegisterCallCount).Should(Equal(1)) 288 _, opts := fakeClient.RegisterArgsForCall(0) 289 Expect(opts.ConnectionDrainTimeout).To(Equal(time.Hour)) 290 }) 291 }) 292 293 Context("when rebalancing is configured", func() { 294 BeforeEach(func() { 295 beacon.RebalanceInterval = 500 * time.Millisecond 296 297 fakeClient.RegisterStub = func(ctx context.Context, opts tsa.RegisterOptions) error { 298 opts.RegisteredFunc() 299 <-ctx.Done() 300 return nil 301 } 302 }) 303 304 It("continuously registers on the configured interval", func() { 305 fuzz := beacon.RebalanceInterval / 2 306 307 before := time.Now() 308 Eventually(fakeClient.RegisterCallCount).Should(Equal(1)) 309 Expect(time.Since(before)).To(BeNumerically("~", 0, fuzz)) 310 311 before = time.Now() 312 Eventually(fakeClient.RegisterCallCount).Should(Equal(2)) 313 Expect(time.Since(before)).To(BeNumerically("~", beacon.RebalanceInterval, fuzz)) 314 315 before = time.Now() 316 Eventually(fakeClient.RegisterCallCount).Should(Equal(3)) 317 Expect(time.Since(before)).To(BeNumerically("~", beacon.RebalanceInterval, fuzz)) 318 }) 319 320 It("cancels the prior registration when the rebalanced registration registers", func() { 321 Eventually(fakeClient.RegisterCallCount).Should(Equal(1)) 322 Consistently(process.Ready()).ShouldNot(Receive()) 323 ctx1, opts1 := fakeClient.RegisterArgsForCall(0) 324 opts1.RegisteredFunc() 325 Eventually(process.Ready()).Should(BeClosed()) 326 327 Eventually(fakeClient.RegisterCallCount).Should(Equal(2)) 328 Consistently(ctx1.Done()).ShouldNot(Receive()) 329 ctx2, opts2 := fakeClient.RegisterArgsForCall(1) 330 opts2.RegisteredFunc() 331 Eventually(ctx1.Done()).Should(BeClosed()) 332 333 Eventually(fakeClient.RegisterCallCount).Should(Equal(3)) 334 Consistently(ctx2.Done()).ShouldNot(Receive()) 335 ctx3, opts3 := fakeClient.RegisterArgsForCall(2) 336 opts3.RegisteredFunc() 337 Eventually(ctx2.Done()).Should(BeClosed()) 338 339 Consistently(ctx3.Done()).ShouldNot(Receive()) 340 }) 341 342 Context("when the maximum number of registrations is reached", func() { 343 var someoneExit chan struct{} 344 345 BeforeEach(func() { 346 someoneExit = make(chan struct{}) 347 348 fakeClient.RegisterStub = func(ctx context.Context, opts tsa.RegisterOptions) error { 349 opts.RegisteredFunc() 350 <-someoneExit 351 return nil 352 } 353 }) 354 355 It("stops rebalancing until one of the registrations exits", func() { 356 Eventually(fakeClient.RegisterCallCount).Should(Equal(5)) 357 Consistently(fakeClient.RegisterCallCount, 2*beacon.RebalanceInterval).Should(Equal(5)) 358 359 someoneExit <- struct{}{} 360 someoneExit <- struct{}{} 361 362 Eventually(fakeClient.RegisterCallCount).Should(Equal(7)) 363 Consistently(fakeClient.RegisterCallCount, 2*beacon.RebalanceInterval).Should(Equal(7)) 364 }) 365 }) 366 367 Context("when the rebalanced registration exits", func() { 368 disaster := errors.New("nope") 369 370 BeforeEach(func() { 371 callTracker := make(chan struct{}, 1) 372 373 fakeClient.RegisterStub = func(ctx context.Context, opts tsa.RegisterOptions) error { 374 select { 375 case callTracker <- struct{}{}: 376 // first call; wait for normal exit 377 opts.RegisteredFunc() 378 <-ctx.Done() 379 return nil 380 default: 381 // second call; simulate error 382 return disaster 383 } 384 } 385 }) 386 387 It("returns its error", func() { 388 Expect(<-process.Wait()).To(Equal(disaster)) 389 }) 390 }) 391 }) 392 393 Context("when registering exits", func() { 394 BeforeEach(func() { 395 fakeClient.RegisterReturns(nil) 396 }) 397 398 It("exits successfully", func() { 399 Expect(<-process.Wait()).ToNot(HaveOccurred()) 400 }) 401 402 Context("with an error", func() { 403 disaster := errors.New("nope") 404 405 BeforeEach(func() { 406 fakeClient.RegisterReturns(disaster) 407 }) 408 409 It("exits with the same error", func() { 410 Expect(<-process.Wait()).To(Equal(disaster)) 411 }) 412 }) 413 }) 414 415 Context("when registration succeeds", func() { 416 It("becomes ready", func() { 417 Consistently(process.Ready()).ShouldNot(Receive()) 418 419 Eventually(fakeClient.RegisterCallCount).Should(Equal(1)) 420 _, opts := fakeClient.RegisterArgsForCall(0) 421 opts.RegisteredFunc() 422 423 Eventually(process.Ready()).Should(BeClosed()) 424 }) 425 }) 426 })