Files
coder/coderd/x/nats/cluster_internal_test.go
T

239 lines
6.1 KiB
Go

package nats
import (
"errors"
"net/url"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/testutil"
)
func Test_parsePeerAddresses(t *testing.T) {
t.Parallel()
t.Run("Valid", func(t *testing.T) {
t.Parallel()
ps := &Pubsub{}
routes, err := ps.parsePeerAddresses([]string{
"whatever://127.0.0.1:4222 ",
"http://[::1]:7222",
"nats://example.com:6222",
})
require.NoError(t, err)
require.ElementsMatch(t, []string{
"nats://127.0.0.1:4222",
"nats://[::1]:7222",
"nats://example.com:6222",
}, routeStrings(routes))
})
// Test that when a pubsub is running with the default port, it assumes all peers are also using
// the default port.
t.Run("PrefersDefaultPort", func(t *testing.T) {
t.Parallel()
ps := &Pubsub{}
ps.opts.ClusterPort = defaultClusterPort
routes, err := ps.parsePeerAddresses([]string{
"whatever://127.0.0.1:4222 ",
"http://[::1]:7222",
"nats://example.com:1234",
})
require.NoError(t, err)
require.ElementsMatch(t, []string{
"nats://127.0.0.1:6222",
"nats://[::1]:6222",
"nats://example.com:6222",
}, routeStrings(routes))
})
t.Run("Empty", func(t *testing.T) {
t.Parallel()
ps := &Pubsub{}
routes, err := ps.parsePeerAddresses(nil)
require.NoError(t, err)
require.Empty(t, routes)
})
t.Run("Dedupes", func(t *testing.T) {
t.Parallel()
ps := &Pubsub{}
routes, err := ps.parsePeerAddresses([]string{
"nats://b.example:6222",
"nats://a.example:6222",
"nats://b.example:6222",
})
require.NoError(t, err)
require.ElementsMatch(t, []string{
"nats://a.example:6222",
"nats://b.example:6222",
}, routeStrings(routes))
})
t.Run("Invalid", func(t *testing.T) {
t.Parallel()
for _, address := range []string{
"",
" ",
"127.0.0.1:4222",
"127.0.0.1",
":4222",
"127.0.0.1:0",
"127.0.0.1:bad",
"nats://127.0.0.1",
"nats://:4222",
"nats://127.0.0.1:0",
"nats://127.0.0.1:bad",
"nats://user@127.0.0.1:4222",
"nats://127.0.0.1:4222/path",
"nats://127.0.0.1:4222?x=1",
"nats://127.0.0.1:4222#frag",
} {
t.Run(address, func(t *testing.T) {
t.Parallel()
ps := &Pubsub{}
_, err := ps.parsePeerAddresses([]string{address})
require.Error(t, err)
})
}
})
}
func Test_filterSelfRoutes(t *testing.T) {
t.Parallel()
ps := &Pubsub{}
routes, err := ps.parsePeerAddresses([]string{
"nats://b.example:6222",
"http://self.example:6222",
})
require.NoError(t, err)
routes = filterSelfRoutes(routes, &url.URL{Scheme: "nats", Host: "self.example:6222"})
require.Equal(t, []string{"nats://b.example:6222"}, routeStrings(routes))
}
func TestPubsub_RefreshPeers(t *testing.T) {
t.Parallel()
t.Run("PeersFetchedOnStartup", func(t *testing.T) {
t.Parallel()
// Supplying PeerFetcher in Options should be enough to seed routes.
// Callers should not need a separate SetPeerFetcher or RefreshPeers call
// after New returns.
fetcher := &testPeerFetcher{addresses: []string{"nats://127.0.0.1:1234"}}
opts := clusterTestOptions(t)
opts.PeerFetcher = fetcher
a := newTestPubsub(t, opts)
require.Eventually(t, func() bool {
routes := currentRouteURLs(a)
return sortedURLsEqual(routes, sortRouteURLs(mustParsePeerAddresses(t,
addrWithAuth(t, "nats://127.0.0.1:1234", opts.ClusterAuthToken),
)))
}, testutil.WaitShort, testutil.IntervalFast)
})
t.Run("SetPeerFetcher", func(t *testing.T) {
t.Parallel()
opts := clusterTestOptions(t)
a := newTestPubsub(t, opts)
routes := []string{
"nats://127.0.0.1:1234",
"nats://127.0.0.1:1235",
}
fetcher := &testPeerFetcher{routes}
expectedRoutes := routesWithAuth(mustParsePeerAddresses(t, fetcher.addresses...), opts.ClusterAuthToken)
a.SetPeerFetcher(fetcher)
require.Eventually(t, func() bool {
return sortedURLsEqual(currentRouteURLs(a), sortRouteURLs(expectedRoutes))
}, testutil.WaitShort, testutil.IntervalFast)
a.SetPeerFetcher(nil)
require.Eventually(t, func() bool {
return sortedURLsEqual(currentRouteURLs(a), nil)
}, testutil.WaitShort, testutil.IntervalFast)
})
}
func mustParsePeerAddresses(t *testing.T, addresses ...string) []*url.URL {
t.Helper()
routes := make([]*url.URL, 0, len(addresses))
for _, address := range addresses {
route, err := url.Parse(address)
require.NoError(t, err)
routes = append(routes, route)
}
return routes
}
func currentRouteURLs(ps *Pubsub) []*url.URL {
ps.clusterMu.Lock()
defer ps.clusterMu.Unlock()
return cloneRouteURLs(ps.currentRoutes)
}
type testPeerFetcher struct {
addresses []string
}
func (f *testPeerFetcher) PrimaryPeerAddresses() []string {
return f.addresses
}
func TestPubsub_setPeerAddresses(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
opts := clusterTestOptions(t)
a := newTestPubsub(t, opts)
b := newTestPubsub(t, opts)
c := newTestPubsub(t, opts)
addrB := clusterRouteAddress(t, b)
addrC := clusterRouteAddress(t, c)
require.NoError(t, a.setPeerAddresses([]string{addrC, addrB}))
requireRoutesEqual(t, a.currentRoutes,
addrWithAuth(t, addrB, opts.ClusterAuthToken),
addrWithAuth(t, addrC, opts.ClusterAuthToken),
)
require.NoError(t, a.setPeerAddresses([]string{addrB, addrC}))
requireRoutesEqual(t, a.currentRoutes,
addrWithAuth(t, addrB, opts.ClusterAuthToken),
addrWithAuth(t, addrC, opts.ClusterAuthToken),
)
require.NoError(t, a.setPeerAddresses(nil))
require.Empty(t, a.currentRoutes)
require.Empty(t, a.serverOpts.Routes)
})
t.Run("StandaloneConfigError", func(t *testing.T) {
t.Parallel()
ps := newTestPubsub(t, defaultTestOptions())
err := ps.setPeerAddresses(nil)
require.ErrorContains(t, err, "not started with clustering enabled")
})
t.Run("Closed", func(t *testing.T) {
t.Parallel()
ps := newTestPubsub(t, clusterTestOptions(t))
require.NoError(t, ps.Close())
err := ps.setPeerAddresses(nil)
require.True(t, errors.Is(err, errClosed), "got %v", err)
})
t.Run("DropsSelfRoute", func(t *testing.T) {
t.Parallel()
ps := newTestPubsub(t, clusterTestOptions(t))
require.NoError(t, ps.setPeerAddresses([]string{clusterRouteAddress(t, ps)}))
require.Empty(t, ps.currentRoutes)
})
}