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  }