github.com/anycable/anycable-go@v1.5.1/broker/nats_test.go (about) 1 package broker 2 3 import ( 4 "context" 5 "fmt" 6 "log/slog" 7 "testing" 8 "time" 9 10 "github.com/anycable/anycable-go/common" 11 "github.com/anycable/anycable-go/enats" 12 natsconfig "github.com/anycable/anycable-go/nats" 13 "github.com/anycable/anycable-go/pubsub" 14 "github.com/nats-io/nats.go/jetstream" 15 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 type FakeBroadastHandler struct { 21 } 22 23 func (FakeBroadastHandler) Broadcast(msg *common.StreamMessage) { 24 } 25 26 func (FakeBroadastHandler) ExecuteRemoteCommand(cmd *common.RemoteCommandMessage) { 27 } 28 29 var _ pubsub.Handler = (*FakeBroadastHandler)(nil) 30 31 func TestNATSBroker_HistorySince_expiration(t *testing.T) { 32 port := 32 33 addr := fmt.Sprintf("nats://127.0.0.1:44%d", port) 34 server, err := startNATSServer(t, addr) 35 require.NoError(t, err) 36 defer server.Shutdown(context.Background()) // nolint:errcheck 37 38 config := NewConfig() 39 config.HistoryTTL = 1 40 41 nconfig := natsconfig.NewNATSConfig() 42 nconfig.Servers = addr 43 44 broadcastHandler := FakeBroadastHandler{} 45 broadcaster := pubsub.NewLegacySubscriber(broadcastHandler) 46 broker := NewNATSBroker(broadcaster, &config, &nconfig, slog.Default()) 47 48 err = broker.Start(nil) 49 require.NoError(t, err) 50 defer broker.Shutdown(context.Background()) // nolint: errcheck 51 52 // Ensure no stream exists 53 require.NoError(t, broker.Reset()) 54 55 // We must subscribe to receive messages from the stream 56 broker.Subscribe("test") 57 defer broker.Unsubscribe("test") 58 59 start := time.Now().Unix() - 10 60 61 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "a"}) 62 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "b"}) 63 64 // Stream must be expired after 1 second 65 time.Sleep(2 * time.Second) 66 67 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "c"}) 68 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "d"}) 69 70 history, err := broker.HistorySince("test", start) 71 require.NoError(t, err) 72 73 require.Len(t, history, 2) 74 assert.EqualValues(t, 3, history[0].Offset) 75 assert.Equal(t, "c", history[0].Data) 76 assert.EqualValues(t, 4, history[1].Offset) 77 assert.Equal(t, "d", history[1].Data) 78 79 // Stream must be expired after 2 seconds 80 time.Sleep(3 * time.Second) 81 82 history, err = broker.HistorySince("test", start) 83 require.NoError(t, err) 84 assert.Empty(t, history) 85 } 86 87 func TestNATSBroker_HistorySince_with_limit(t *testing.T) { 88 port := 33 89 addr := fmt.Sprintf("nats://127.0.0.1:44%d", port) 90 server, err := startNATSServer(t, addr) 91 require.NoError(t, err) 92 defer server.Shutdown(context.Background()) // nolint:errcheck 93 94 config := NewConfig() 95 config.HistoryLimit = 2 96 97 nconfig := natsconfig.NewNATSConfig() 98 nconfig.Servers = addr 99 100 broadcastHandler := FakeBroadastHandler{} 101 broadcaster := pubsub.NewLegacySubscriber(broadcastHandler) 102 broker := NewNATSBroker(broadcaster, &config, &nconfig, slog.Default()) 103 104 err = broker.Start(nil) 105 require.NoError(t, err) 106 defer broker.Shutdown(context.Background()) // nolint: errcheck 107 108 // Ensure no stream exists 109 require.NoError(t, broker.Reset()) 110 111 // We must subscribe to receive messages from the stream 112 broker.Subscribe("test") 113 defer broker.Unsubscribe("test") 114 115 start := time.Now().Unix() - 10 116 117 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "a"}) 118 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "b"}) 119 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "c"}) 120 121 history, err := broker.HistorySince("test", start) 122 require.NoError(t, err) 123 124 assert.Len(t, history, 2) 125 assert.EqualValues(t, 3, history[1].Offset) 126 assert.Equal(t, "c", history[1].Data) 127 } 128 129 func TestNATSBroker_HistoryFrom(t *testing.T) { 130 port := 34 131 addr := fmt.Sprintf("nats://127.0.0.1:44%d", port) 132 server, err := startNATSServer(t, addr) 133 require.NoError(t, err) 134 defer server.Shutdown(context.Background()) // nolint:errcheck 135 136 config := NewConfig() 137 138 nconfig := natsconfig.NewNATSConfig() 139 nconfig.Servers = addr 140 141 broadcastHandler := FakeBroadastHandler{} 142 broadcaster := pubsub.NewLegacySubscriber(broadcastHandler) 143 broker := NewNATSBroker(broadcaster, &config, &nconfig, slog.Default()) 144 145 err = broker.Start(nil) 146 require.NoError(t, err) 147 defer broker.Shutdown(context.Background()) // nolint: errcheck 148 149 // Ensure no stream exists 150 require.NoError(t, broker.Reset()) 151 152 // We must subscribe to receive messages from the stream 153 broker.Subscribe("test") 154 defer broker.Unsubscribe("test") 155 156 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "y"}) 157 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "z"}) 158 159 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "a"}) 160 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "b"}) 161 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "c"}) 162 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "d"}) 163 broker.HandleBroadcast(&common.StreamMessage{Stream: "test", Data: "e"}) 164 165 // We obtain sequence numbers with some offset to ensure that we use sequence numbers 166 // as stream offsets 167 seq := consumerSequence(broker.js, "test", 3) 168 169 offsets, err := seq.read(5) 170 require.NoError(t, err) 171 172 t.Run("With current epoch", func(t *testing.T) { 173 require.EqualValues(t, 4, offsets[1]) 174 175 history, err := broker.HistoryFrom("test", broker.Epoch(), offsets[1]) 176 require.NoError(t, err) 177 178 assert.Len(t, history, 3) 179 assert.EqualValues(t, 5, history[0].Offset) 180 assert.Equal(t, "c", history[0].Data) 181 assert.EqualValues(t, 6, history[1].Offset) 182 assert.Equal(t, "d", history[1].Data) 183 assert.EqualValues(t, 7, history[2].Offset) 184 assert.Equal(t, "e", history[2].Data) 185 }) 186 187 t.Run("When no new messages", func(t *testing.T) { 188 history, err := broker.HistoryFrom("test", broker.Epoch(), offsets[4]) 189 require.NoError(t, err) 190 assert.Len(t, history, 0) 191 }) 192 193 t.Run("When no stream", func(t *testing.T) { 194 history, err := broker.HistoryFrom("unknown", broker.Epoch(), offsets[1]) 195 require.Error(t, err) 196 assert.Nil(t, history) 197 }) 198 199 t.Run("With unknown epoch", func(t *testing.T) { 200 history, err := broker.HistoryFrom("test", "unknown", offsets[1]) 201 require.Error(t, err) 202 require.Nil(t, history) 203 }) 204 } 205 206 type TestCacheable struct { 207 data string 208 } 209 210 func (t *TestCacheable) ToCacheEntry() ([]byte, error) { 211 return []byte(t.data), nil 212 } 213 214 func TestNATSBroker_Sessions(t *testing.T) { 215 port := 41 216 addr := fmt.Sprintf("nats://127.0.0.1:44%d", port) 217 server, err := startNATSServer(t, addr) 218 require.NoError(t, err) 219 defer server.Shutdown(context.Background()) // nolint:errcheck 220 221 config := NewConfig() 222 config.SessionsTTL = 1 223 224 nconfig := natsconfig.NewNATSConfig() 225 nconfig.Servers = addr 226 227 broker := NewNATSBroker(nil, &config, &nconfig, slog.Default()) 228 229 err = broker.Start(nil) 230 require.NoError(t, err) 231 232 defer broker.Shutdown(context.Background()) // nolint: errcheck 233 234 err = broker.CommitSession("test123", &TestCacheable{"cache-me"}) 235 require.NoError(t, err) 236 237 anotherBroker := NewNATSBroker(nil, &config, &nconfig, slog.Default()) 238 require.NoError(t, anotherBroker.Start(nil)) 239 defer anotherBroker.Shutdown(context.Background()) // nolint: errcheck 240 241 require.NoError(t, anotherBroker.Ready()) 242 243 restored, err := anotherBroker.RestoreSession("test123") 244 245 require.NoError(t, err) 246 assert.Equalf(t, []byte("cache-me"), restored, "Expected to restore session data: %s", restored) 247 248 // Expiration 249 time.Sleep(2 * time.Second) 250 251 expired, err := broker.RestoreSession("test123") 252 require.NoError(t, err) 253 assert.Nil(t, expired) 254 255 err = broker.CommitSession("test345", &TestCacheable{"cache-me-again"}) 256 require.NoError(t, err) 257 258 err = broker.FinishSession("test345") 259 require.NoError(t, err) 260 261 finished, err := anotherBroker.RestoreSession("test345") 262 263 require.NoError(t, err) 264 assert.Equal(t, []byte("cache-me-again"), finished) 265 266 // Expiration 267 time.Sleep(2 * time.Second) 268 269 finishedStale, err := anotherBroker.RestoreSession("test345") 270 require.NoError(t, err) 271 assert.Nil(t, finishedStale) 272 } 273 274 func TestNATSBroker_SessionsTTLChange(t *testing.T) { 275 port := 43 276 addr := fmt.Sprintf("nats://127.0.0.1:44%d", port) 277 278 server, err := startNATSServer(t, addr) 279 require.NoError(t, err) 280 defer server.Shutdown(context.Background()) // nolint:errcheck 281 282 config := NewConfig() 283 config.SessionsTTL = 1 284 285 nconfig := natsconfig.NewNATSConfig() 286 nconfig.Servers = addr 287 288 broker := NewNATSBroker(nil, &config, &nconfig, slog.Default()) 289 290 err = broker.Start(nil) 291 require.NoError(t, err) 292 293 defer broker.Shutdown(context.Background()) // nolint: errcheck 294 295 require.NoError(t, broker.Ready()) 296 297 err = broker.CommitSession("test123", &TestCacheable{"cache-me"}) 298 require.NoError(t, err) 299 300 aConfig := NewConfig() 301 aConfig.SessionsTTL = 3 302 303 anotherBroker := NewNATSBroker(nil, &aConfig, &nconfig, slog.Default()) 304 require.NoError(t, anotherBroker.Start(nil)) 305 defer anotherBroker.Shutdown(context.Background()) // nolint: errcheck 306 307 require.NoError(t, anotherBroker.Ready()) 308 309 // The session must be missing since we recreated the bucket due to TTL change 310 missing, err := anotherBroker.RestoreSession("test123") 311 312 require.NoError(t, err) 313 assert.Nil(t, missing) 314 315 err = anotherBroker.CommitSession("test234", &TestCacheable{"cache-me-again"}) 316 require.NoError(t, err) 317 318 time.Sleep(1 * time.Second) 319 320 // Shouldn't fail and catch up a new bucket 321 restored, err := broker.RestoreSession("test234") 322 require.NoError(t, err) 323 assert.Equalf(t, []byte("cache-me-again"), restored, "Expected to restore session data: %s", restored) 324 325 // Touch session 326 err = anotherBroker.FinishSession("test234") 327 require.NoError(t, err) 328 329 time.Sleep(2 * time.Second) 330 331 restoredAgain, err := broker.RestoreSession("test234") 332 require.NoError(t, err) 333 assert.NotNil(t, restoredAgain) 334 335 time.Sleep(1 * time.Second) 336 337 expired, err := broker.RestoreSession("test234") 338 require.NoError(t, err) 339 assert.Nil(t, expired) 340 } 341 342 func TestNATSBroker_Epoch(t *testing.T) { 343 port := 45 344 addr := fmt.Sprintf("nats://127.0.0.1:44%d", port) 345 346 server, err := startNATSServer(t, addr) 347 require.NoError(t, err) 348 defer server.Shutdown(context.Background()) // nolint:errcheck 349 350 config := NewConfig() 351 352 nconfig := natsconfig.NewNATSConfig() 353 nconfig.Servers = addr 354 355 broker := NewNATSBroker(nil, &config, &nconfig, slog.Default()) 356 357 err = broker.Start(nil) 358 require.NoError(t, err) 359 defer broker.Shutdown(context.Background()) // nolint: errcheck 360 361 require.NoError(t, broker.Ready()) 362 broker.Reset() // nolint: errcheck 363 364 epoch := broker.Epoch() 365 366 anotherBroker := NewNATSBroker(nil, &config, &nconfig, slog.Default()) 367 require.NoError(t, anotherBroker.Start(nil)) 368 defer anotherBroker.Shutdown(context.Background()) // nolint: errcheck 369 370 require.NoError(t, anotherBroker.Ready()) 371 372 assert.Equal(t, epoch, anotherBroker.Epoch()) 373 374 // Now let's test that epoch changes are picked up 375 require.NoError(t, anotherBroker.SetEpoch("new-epoch")) 376 377 assert.Equal(t, "new-epoch", anotherBroker.Epoch()) 378 assert.Equal(t, "new-epoch", anotherBroker.local.GetEpoch()) 379 380 timer := time.After(2 * time.Second) 381 382 wait: 383 for { 384 select { 385 case <-timer: 386 assert.Fail(t, "Epoch change wasn't picked up") 387 return 388 default: 389 if broker.Epoch() == "new-epoch" { 390 break wait 391 } 392 393 time.Sleep(100 * time.Millisecond) 394 } 395 } 396 } 397 398 func startNATSServer(t *testing.T, addr string) (*enats.Service, error) { 399 conf := enats.NewConfig() 400 conf.JetStream = true 401 conf.ServiceAddr = addr 402 conf.StoreDir = t.TempDir() 403 service := enats.NewService(&conf, slog.Default()) 404 405 err := service.Start() 406 if err != nil { 407 return nil, err 408 } 409 410 err = service.WaitJetStreamReady(5) 411 if err != nil { 412 return nil, err 413 } 414 415 return service, nil 416 } 417 418 type consumerSequenceReader struct { 419 seq chan uint64 420 } 421 422 func (r *consumerSequenceReader) read(n int) ([]uint64, error) { 423 seq := make([]uint64, n) 424 i := 0 425 426 for { 427 select { 428 case v := <-r.seq: 429 seq[i] = v 430 i++ 431 if i == n { 432 return seq, nil 433 } 434 case <-time.After(1 * time.Second): 435 return nil, fmt.Errorf("timed out to read from consumer; received %d messages, expected %d", i, n) 436 } 437 } 438 } 439 440 func consumerSequence(js jetstream.JetStream, stream string, offset uint64) *consumerSequenceReader { 441 seq := make(chan uint64) 442 443 conf := jetstream.ConsumerConfig{ 444 AckPolicy: jetstream.AckExplicitPolicy, 445 } 446 447 if offset > 0 { 448 conf.OptStartSeq = offset 449 conf.DeliverPolicy = jetstream.DeliverByStartSequencePolicy 450 } 451 452 cons, err := js.CreateConsumer(context.Background(), streamPrefix+stream, conf) 453 454 if err != nil { 455 panic(err) 456 } 457 458 cons.Consume(func(msg jetstream.Msg) { // nolint:errcheck 459 meta, _ := msg.Metadata() 460 seq <- meta.Sequence.Stream 461 }) 462 463 return &consumerSequenceReader{seq} 464 }