github.imxd.top/hashicorp/consul@v1.4.5/agent/ae/ae_test.go (about) 1 package ae 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 "reflect" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/hashicorp/consul/lib" 14 "github.com/stretchr/testify/assert" 15 ) 16 17 func TestAE_scaleFactor(t *testing.T) { 18 t.Parallel() 19 tests := []struct { 20 nodes int 21 scale int 22 }{ 23 {100, 1}, 24 {200, 2}, 25 {1000, 4}, 26 {10000, 8}, 27 } 28 for _, tt := range tests { 29 t.Run(fmt.Sprintf("%d nodes", tt.nodes), func(t *testing.T) { 30 if got, want := scaleFactor(tt.nodes), tt.scale; got != want { 31 t.Fatalf("got scale factor %d want %d", got, want) 32 } 33 }) 34 } 35 } 36 37 func TestAE_Pause_nestedPauseResume(t *testing.T) { 38 t.Parallel() 39 l := NewStateSyncer(nil, 0, nil, nil) 40 if l.Paused() != false { 41 t.Fatal("syncer should be unPaused after init") 42 } 43 l.Pause() 44 if l.Paused() != true { 45 t.Fatal("syncer should be Paused after first call to Pause()") 46 } 47 l.Pause() 48 if l.Paused() != true { 49 t.Fatal("syncer should STILL be Paused after second call to Pause()") 50 } 51 gotR := l.Resume() 52 if l.Paused() != true { 53 t.Fatal("syncer should STILL be Paused after FIRST call to Resume()") 54 } 55 assert.False(t, gotR) 56 gotR = l.Resume() 57 if l.Paused() != false { 58 t.Fatal("syncer should NOT be Paused after SECOND call to Resume()") 59 } 60 assert.True(t, gotR) 61 62 defer func() { 63 err := recover() 64 if err == nil { 65 t.Fatal("unbalanced Resume() should panic") 66 } 67 }() 68 l.Resume() 69 } 70 71 func TestAE_Pause_ResumeTriggersSyncChanges(t *testing.T) { 72 l := NewStateSyncer(nil, 0, nil, nil) 73 l.Pause() 74 l.Resume() 75 select { 76 case <-l.SyncChanges.Notif(): 77 // expected 78 case <-l.SyncFull.Notif(): 79 t.Fatal("resume triggered SyncFull instead of SyncChanges") 80 default: 81 t.Fatal("resume did not trigger SyncFull") 82 } 83 } 84 85 func TestAE_staggerDependsOnClusterSize(t *testing.T) { 86 libRandomStagger = func(d time.Duration) time.Duration { return d } 87 defer func() { libRandomStagger = lib.RandomStagger }() 88 89 l := testSyncer() 90 if got, want := l.staggerFn(10*time.Millisecond), 10*time.Millisecond; got != want { 91 t.Fatalf("got %v want %v", got, want) 92 } 93 l.ClusterSize = func() int { return 256 } 94 if got, want := l.staggerFn(10*time.Millisecond), 20*time.Millisecond; got != want { 95 t.Fatalf("got %v want %v", got, want) 96 } 97 } 98 99 func TestAE_Run_SyncFullBeforeChanges(t *testing.T) { 100 shutdownCh := make(chan struct{}) 101 state := &mock{ 102 syncChanges: func() error { 103 close(shutdownCh) 104 return nil 105 }, 106 } 107 108 // indicate that we have partial changes before starting Run 109 l := testSyncer() 110 l.State = state 111 l.ShutdownCh = shutdownCh 112 l.SyncChanges.Trigger() 113 114 var wg sync.WaitGroup 115 wg.Add(1) 116 go func() { 117 defer wg.Done() 118 l.Run() 119 }() 120 wg.Wait() 121 122 if got, want := state.seq, []string{"full", "changes"}; !reflect.DeepEqual(got, want) { 123 t.Fatalf("got call sequence %v want %v", got, want) 124 } 125 } 126 127 func TestAE_Run_Quit(t *testing.T) { 128 t.Run("Run panics without ClusterSize", func(t *testing.T) { 129 defer func() { 130 err := recover() 131 if err == nil { 132 t.Fatal("Run should panic") 133 } 134 }() 135 l := testSyncer() 136 l.ClusterSize = nil 137 l.Run() 138 }) 139 t.Run("runFSM quits", func(t *testing.T) { 140 // start timer which explodes if runFSM does not quit 141 tm := time.AfterFunc(time.Second, func() { panic("timeout") }) 142 143 l := testSyncer() 144 l.runFSM(fullSyncState, func(fsmState) fsmState { return doneState }) 145 // should just quit 146 tm.Stop() 147 }) 148 } 149 150 func TestAE_FSM(t *testing.T) { 151 t.Run("fullSyncState", func(t *testing.T) { 152 t.Run("Paused -> retryFullSyncState", func(t *testing.T) { 153 l := testSyncer() 154 l.Pause() 155 fs := l.nextFSMState(fullSyncState) 156 if got, want := fs, retryFullSyncState; got != want { 157 t.Fatalf("got state %v want %v", got, want) 158 } 159 }) 160 t.Run("SyncFull() error -> retryFullSyncState", func(t *testing.T) { 161 l := testSyncer() 162 l.State = &mock{syncFull: func() error { return errors.New("boom") }} 163 fs := l.nextFSMState(fullSyncState) 164 if got, want := fs, retryFullSyncState; got != want { 165 t.Fatalf("got state %v want %v", got, want) 166 } 167 }) 168 t.Run("SyncFull() OK -> partialSyncState", func(t *testing.T) { 169 l := testSyncer() 170 l.State = &mock{} 171 fs := l.nextFSMState(fullSyncState) 172 if got, want := fs, partialSyncState; got != want { 173 t.Fatalf("got state %v want %v", got, want) 174 } 175 }) 176 }) 177 178 t.Run("retryFullSyncState", func(t *testing.T) { 179 // helper for testing state transitions from retrySyncFullState 180 test := func(ev event, to fsmState) { 181 l := testSyncer() 182 l.retrySyncFullEvent = func() event { return ev } 183 fs := l.nextFSMState(retryFullSyncState) 184 if got, want := fs, to; got != want { 185 t.Fatalf("got state %v want %v", got, want) 186 } 187 } 188 t.Run("shutdownEvent -> doneState", func(t *testing.T) { 189 test(shutdownEvent, doneState) 190 }) 191 t.Run("syncFullNotifEvent -> fullSyncState", func(t *testing.T) { 192 test(syncFullNotifEvent, fullSyncState) 193 }) 194 t.Run("syncFullTimerEvent -> fullSyncState", func(t *testing.T) { 195 test(syncFullTimerEvent, fullSyncState) 196 }) 197 t.Run("invalid event -> panic ", func(t *testing.T) { 198 defer func() { 199 err := recover() 200 if err == nil { 201 t.Fatal("invalid event should panic") 202 } 203 }() 204 test(event("invalid"), fsmState("")) 205 }) 206 }) 207 208 t.Run("partialSyncState", func(t *testing.T) { 209 // helper for testing state transitions from partialSyncState 210 test := func(ev event, to fsmState) { 211 l := testSyncer() 212 l.syncChangesEvent = func() event { return ev } 213 fs := l.nextFSMState(partialSyncState) 214 if got, want := fs, to; got != want { 215 t.Fatalf("got state %v want %v", got, want) 216 } 217 } 218 t.Run("shutdownEvent -> doneState", func(t *testing.T) { 219 test(shutdownEvent, doneState) 220 }) 221 t.Run("syncFullNotifEvent -> fullSyncState", func(t *testing.T) { 222 test(syncFullNotifEvent, fullSyncState) 223 }) 224 t.Run("syncFullTimerEvent -> fullSyncState", func(t *testing.T) { 225 test(syncFullTimerEvent, fullSyncState) 226 }) 227 t.Run("syncChangesEvent+Paused -> partialSyncState", func(t *testing.T) { 228 l := testSyncer() 229 l.Pause() 230 l.syncChangesEvent = func() event { return syncChangesNotifEvent } 231 fs := l.nextFSMState(partialSyncState) 232 if got, want := fs, partialSyncState; got != want { 233 t.Fatalf("got state %v want %v", got, want) 234 } 235 }) 236 t.Run("syncChangesEvent+SyncChanges() error -> partialSyncState", func(t *testing.T) { 237 l := testSyncer() 238 l.State = &mock{syncChanges: func() error { return errors.New("boom") }} 239 l.syncChangesEvent = func() event { return syncChangesNotifEvent } 240 fs := l.nextFSMState(partialSyncState) 241 if got, want := fs, partialSyncState; got != want { 242 t.Fatalf("got state %v want %v", got, want) 243 } 244 }) 245 t.Run("syncChangesEvent+SyncChanges() OK -> partialSyncState", func(t *testing.T) { 246 l := testSyncer() 247 l.State = &mock{} 248 l.syncChangesEvent = func() event { return syncChangesNotifEvent } 249 fs := l.nextFSMState(partialSyncState) 250 if got, want := fs, partialSyncState; got != want { 251 t.Fatalf("got state %v want %v", got, want) 252 } 253 }) 254 t.Run("invalid event -> panic ", func(t *testing.T) { 255 defer func() { 256 err := recover() 257 if err == nil { 258 t.Fatal("invalid event should panic") 259 } 260 }() 261 test(event("invalid"), fsmState("")) 262 }) 263 }) 264 t.Run("invalid state -> panic ", func(t *testing.T) { 265 defer func() { 266 err := recover() 267 if err == nil { 268 t.Fatal("invalid state should panic") 269 } 270 }() 271 l := testSyncer() 272 l.nextFSMState(fsmState("invalid")) 273 }) 274 } 275 276 func TestAE_RetrySyncFullEvent(t *testing.T) { 277 t.Run("trigger shutdownEvent", func(t *testing.T) { 278 l := testSyncer() 279 l.ShutdownCh = make(chan struct{}) 280 evch := make(chan event) 281 go func() { evch <- l.retrySyncFullEvent() }() 282 close(l.ShutdownCh) 283 if got, want := <-evch, shutdownEvent; got != want { 284 t.Fatalf("got event %q want %q", got, want) 285 } 286 }) 287 t.Run("trigger shutdownEvent during FullNotif", func(t *testing.T) { 288 l := testSyncer() 289 l.ShutdownCh = make(chan struct{}) 290 evch := make(chan event) 291 go func() { evch <- l.retrySyncFullEvent() }() 292 l.SyncFull.Trigger() 293 time.Sleep(100 * time.Millisecond) 294 close(l.ShutdownCh) 295 if got, want := <-evch, shutdownEvent; got != want { 296 t.Fatalf("got event %q want %q", got, want) 297 } 298 }) 299 t.Run("trigger syncFullNotifEvent", func(t *testing.T) { 300 l := testSyncer() 301 l.serverUpInterval = 10 * time.Millisecond 302 evch := make(chan event) 303 go func() { evch <- l.retrySyncFullEvent() }() 304 l.SyncFull.Trigger() 305 if got, want := <-evch, syncFullNotifEvent; got != want { 306 t.Fatalf("got event %q want %q", got, want) 307 } 308 }) 309 t.Run("trigger syncFullTimerEvent", func(t *testing.T) { 310 l := testSyncer() 311 l.retryFailInterval = 10 * time.Millisecond 312 evch := make(chan event) 313 go func() { evch <- l.retrySyncFullEvent() }() 314 if got, want := <-evch, syncFullTimerEvent; got != want { 315 t.Fatalf("got event %q want %q", got, want) 316 } 317 }) 318 } 319 320 func TestAE_SyncChangesEvent(t *testing.T) { 321 t.Run("trigger shutdownEvent", func(t *testing.T) { 322 l := testSyncer() 323 l.ShutdownCh = make(chan struct{}) 324 evch := make(chan event) 325 go func() { evch <- l.syncChangesEvent() }() 326 close(l.ShutdownCh) 327 if got, want := <-evch, shutdownEvent; got != want { 328 t.Fatalf("got event %q want %q", got, want) 329 } 330 }) 331 t.Run("trigger shutdownEvent during FullNotif", func(t *testing.T) { 332 l := testSyncer() 333 l.ShutdownCh = make(chan struct{}) 334 evch := make(chan event) 335 go func() { evch <- l.syncChangesEvent() }() 336 l.SyncFull.Trigger() 337 time.Sleep(100 * time.Millisecond) 338 close(l.ShutdownCh) 339 if got, want := <-evch, shutdownEvent; got != want { 340 t.Fatalf("got event %q want %q", got, want) 341 } 342 }) 343 t.Run("trigger syncFullNotifEvent", func(t *testing.T) { 344 l := testSyncer() 345 l.serverUpInterval = 10 * time.Millisecond 346 evch := make(chan event) 347 go func() { evch <- l.syncChangesEvent() }() 348 l.SyncFull.Trigger() 349 if got, want := <-evch, syncFullNotifEvent; got != want { 350 t.Fatalf("got event %q want %q", got, want) 351 } 352 }) 353 t.Run("trigger syncFullTimerEvent", func(t *testing.T) { 354 l := testSyncer() 355 l.Interval = 10 * time.Millisecond 356 evch := make(chan event) 357 go func() { evch <- l.syncChangesEvent() }() 358 if got, want := <-evch, syncFullTimerEvent; got != want { 359 t.Fatalf("got event %q want %q", got, want) 360 } 361 }) 362 t.Run("trigger syncChangesNotifEvent", func(t *testing.T) { 363 l := testSyncer() 364 evch := make(chan event) 365 go func() { evch <- l.syncChangesEvent() }() 366 l.SyncChanges.Trigger() 367 if got, want := <-evch, syncChangesNotifEvent; got != want { 368 t.Fatalf("got event %q want %q", got, want) 369 } 370 }) 371 } 372 373 type mock struct { 374 seq []string 375 syncFull, syncChanges func() error 376 } 377 378 func (m *mock) SyncFull() error { 379 m.seq = append(m.seq, "full") 380 if m.syncFull != nil { 381 return m.syncFull() 382 } 383 return nil 384 } 385 386 func (m *mock) SyncChanges() error { 387 m.seq = append(m.seq, "changes") 388 if m.syncChanges != nil { 389 return m.syncChanges() 390 } 391 return nil 392 } 393 394 func testSyncer() *StateSyncer { 395 logger := log.New(os.Stderr, "", 0) 396 l := NewStateSyncer(nil, time.Second, nil, logger) 397 l.stagger = func(d time.Duration) time.Duration { return d } 398 l.ClusterSize = func() int { return 1 } 399 return l 400 }