github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/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/pf-qiu/concourse/v6/tsa"
    12  	"github.com/pf-qiu/concourse/v6/worker"
    13  	"github.com/pf-qiu/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  })