Files
nats-server/test/route_discovery_test.go
Ivan Kozlovic 22cff99e58 [Fixed] Profiling and Monitoring timeout issues
The http servers for those two were recently modified to set
a ReadTimeout and WriteTimeout. The WriteTimeout specifically
caused issues for Profiling since it is common to ask sampling
of several seconds. Pprof code would reject the request if it
detected that http server's WriteTimeout was more than sampling
in request.
For monitoring, any situation that would cause the monitoring code
to take more than 2 seconds to gather information (could be due
to locking, amount of objects to return, time required for sorting,
etc..) would also cause cURL to return empty response or WebBrowser
to fail to display the page.

Resolves #600
2017-11-08 14:58:10 -07:00

697 lines
19 KiB
Go

// Copyright 2015-2016 Apcera Inc. All rights reserved.
package test
import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"runtime"
"strconv"
"strings"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
)
func runSeedServer(t *testing.T) (*server.Server, *server.Options) {
return RunServerWithConfig("./configs/seed.conf")
}
func runAuthSeedServer(t *testing.T) (*server.Server, *server.Options) {
return RunServerWithConfig("./configs/auth_seed.conf")
}
func TestSeedFirstRouteInfo(t *testing.T) {
s, opts := runSeedServer(t)
defer s.Shutdown()
rc := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc.Close()
_, routeExpect := setupRoute(t, rc, opts)
buf := routeExpect(infoRe)
info := server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.ID != s.ID() {
t.Fatalf("Expected seed's ID %q, got %q", s.ID(), info.ID)
}
}
func TestSeedMultipleRouteInfo(t *testing.T) {
s, opts := runSeedServer(t)
defer s.Shutdown()
rc1 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc1.Close()
rc1ID := "2222"
rc1Port := 22
rc1Host := "127.0.0.1"
routeSend1, route1Expect := setupRouteEx(t, rc1, opts, rc1ID)
route1Expect(infoRe)
// register ourselves via INFO
r1Info := server.Info{ID: rc1ID, Host: rc1Host, Port: rc1Port}
b, _ := json.Marshal(r1Info)
infoJSON := fmt.Sprintf(server.InfoProto, b)
routeSend1(infoJSON)
routeSend1("PING\r\n")
route1Expect(pongRe)
rc2 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc2.Close()
rc2ID := "2224"
rc2Port := 24
rc2Host := "127.0.0.1"
routeSend2, route2Expect := setupRouteEx(t, rc2, opts, rc2ID)
hp2 := fmt.Sprintf("nats-route://%s/", net.JoinHostPort(rc2Host, strconv.Itoa(rc2Port)))
// register ourselves via INFO
r2Info := server.Info{ID: rc2ID, Host: rc2Host, Port: rc2Port}
b, _ = json.Marshal(r2Info)
infoJSON = fmt.Sprintf(server.InfoProto, b)
routeSend2(infoJSON)
// Now read back the second INFO route1 should receive letting
// it know about route2
buf := route1Expect(infoRe)
info := server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.ID != rc2ID {
t.Fatalf("Expected info.ID to be %q, got %q", rc2ID, info.ID)
}
if info.IP == "" {
t.Fatalf("Expected a IP for the implicit route")
}
if info.IP != hp2 {
t.Fatalf("Expected IP Host of %s, got %s\n", hp2, info.IP)
}
route2Expect(infoRe)
routeSend2("PING\r\n")
route2Expect(pongRe)
// Now let's do a third.
rc3 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc3.Close()
rc3ID := "2226"
rc3Port := 26
rc3Host := "127.0.0.1"
routeSend3, _ := setupRouteEx(t, rc3, opts, rc3ID)
// register ourselves via INFO
r3Info := server.Info{ID: rc3ID, Host: rc3Host, Port: rc3Port}
b, _ = json.Marshal(r3Info)
infoJSON = fmt.Sprintf(server.InfoProto, b)
routeSend3(infoJSON)
// Now read back out the info from the seed route
buf = route1Expect(infoRe)
info = server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.ID != rc3ID {
t.Fatalf("Expected info.ID to be %q, got %q", rc3ID, info.ID)
}
// Now read back out the info from the seed route
buf = route2Expect(infoRe)
info = server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.ID != rc3ID {
t.Fatalf("Expected info.ID to be %q, got %q", rc3ID, info.ID)
}
}
func TestSeedSolicitWorks(t *testing.T) {
s1, opts := runSeedServer(t)
defer s1.Shutdown()
// Create the routes string for others to connect to the seed.
routesStr := fmt.Sprintf("nats-route://%s:%d/", opts.Cluster.Host, opts.Cluster.Port)
// Run Server #2
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s2 := RunServer(s2Opts)
defer s2.Shutdown()
// Run Server #3
s3Opts := nextServerOpts(s2Opts)
s3 := RunServer(s3Opts)
defer s3.Shutdown()
// Wait for a bit for graph to connect
time.Sleep(500 * time.Millisecond)
// Grab Routez from monitor ports, make sure we are fully connected
url := fmt.Sprintf("http://%s:%d/", opts.Host, opts.HTTPPort)
rz := readHTTPRoutez(t, url)
ris := expectRids(t, rz, []string{s2.ID(), s3.ID()})
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s2Opts.Host, s2Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s3.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s3Opts.Host, s3Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s2.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
}
type serverInfo struct {
server *server.Server
opts *server.Options
}
func checkConnected(t *testing.T, servers []serverInfo, current int, oneSeed bool) error {
s := servers[current]
// Grab Routez from monitor ports, make sure we are fully connected
url := fmt.Sprintf("http://%s:%d/", s.opts.Host, s.opts.HTTPPort)
rz := readHTTPRoutez(t, url)
total := len(servers)
var ids []string
for i := 0; i < total; i++ {
if i == current {
continue
}
ids = append(ids, servers[i].server.ID())
}
ris, err := expectRidsNoFatal(t, true, rz, ids)
if err != nil {
return err
}
for i := 0; i < total; i++ {
if i == current {
continue
}
s := servers[i]
if current == 0 || ((oneSeed && i > 0) || (!oneSeed && (i != current-1))) {
if ris[s.server.ID()].IsConfigured {
return fmt.Errorf("Expected server %s:%d not to be configured", s.opts.Host, s.opts.Port)
}
} else if oneSeed || (i == current-1) {
if !ris[s.server.ID()].IsConfigured {
return fmt.Errorf("Expected server %s:%d to be configured", s.opts.Host, s.opts.Port)
}
}
}
return nil
}
func TestStressSeedSolicitWorks(t *testing.T) {
s1, opts := runSeedServer(t)
defer s1.Shutdown()
// Create the routes string for others to connect to the seed.
routesStr := fmt.Sprintf("nats-route://%s:%d/", opts.Cluster.Host, opts.Cluster.Port)
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s3Opts := nextServerOpts(s2Opts)
s4Opts := nextServerOpts(s3Opts)
for i := 0; i < 10; i++ {
func() {
// Run these servers manually, because we want them to start and
// connect to s1 as fast as possible.
s2 := server.New(s2Opts)
if s2 == nil {
panic("No NATS Server object returned.")
}
defer s2.Shutdown()
go s2.Start()
s3 := server.New(s3Opts)
if s3 == nil {
panic("No NATS Server object returned.")
}
defer s3.Shutdown()
go s3.Start()
s4 := server.New(s4Opts)
if s4 == nil {
panic("No NATS Server object returned.")
}
defer s4.Shutdown()
go s4.Start()
serversInfo := []serverInfo{{s1, opts}, {s2, s2Opts}, {s3, s3Opts}, {s4, s4Opts}}
var err error
maxTime := time.Now().Add(5 * time.Second)
for time.Now().Before(maxTime) {
for j := 0; j < len(serversInfo); j++ {
err = checkConnected(t, serversInfo, j, true)
// If error, start this for loop from beginning
if err != nil {
// Sleep a bit before the next attempt
time.Sleep(100 * time.Millisecond)
break
}
}
// All servers checked ok, we are done, otherwise, try again
// until time is up
if err == nil {
break
}
}
// Report error
if err != nil {
t.Fatalf("Error: %v", err)
}
}()
maxTime := time.Now().Add(2 * time.Second)
for time.Now().Before(maxTime) {
if s1.NumRoutes() > 0 {
time.Sleep(10 * time.Millisecond)
} else {
break
}
}
}
}
func TestChainedSolicitWorks(t *testing.T) {
s1, opts := runSeedServer(t)
defer s1.Shutdown()
// Create the routes string for others to connect to the seed.
routesStr := fmt.Sprintf("nats-route://%s:%d/", opts.Cluster.Host, opts.Cluster.Port)
// Run Server #2
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s2 := RunServer(s2Opts)
defer s2.Shutdown()
// Run Server #3
s3Opts := nextServerOpts(s2Opts)
// We will have s3 connect to s2, not the seed.
routesStr = fmt.Sprintf("nats-route://%s:%d/", s2Opts.Cluster.Host, s2Opts.Cluster.Port)
s3Opts.Routes = server.RoutesFromStr(routesStr)
s3 := RunServer(s3Opts)
defer s3.Shutdown()
// Wait for a bit for graph to connect
time.Sleep(500 * time.Millisecond)
// Grab Routez from monitor ports, make sure we are fully connected
url := fmt.Sprintf("http://%s:%d/", opts.Host, opts.HTTPPort)
rz := readHTTPRoutez(t, url)
ris := expectRids(t, rz, []string{s2.ID(), s3.ID()})
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s2Opts.Host, s2Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s3.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s3Opts.Host, s3Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s2.ID()})
if !ris[s2.ID()].IsConfigured {
t.Fatalf("Expected s2 server to be configured\n")
}
if ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server not to be configured\n")
}
}
func TestStressChainedSolicitWorks(t *testing.T) {
s1, opts := runSeedServer(t)
defer s1.Shutdown()
// Create the routes string for s2 to connect to the seed
routesStr := fmt.Sprintf("nats-route://%s:%d/", opts.Cluster.Host, opts.Cluster.Port)
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s3Opts := nextServerOpts(s2Opts)
// Create the routes string for s3 to connect to s2
routesStr = fmt.Sprintf("nats-route://%s:%d/", s2Opts.Cluster.Host, s2Opts.Cluster.Port)
s3Opts.Routes = server.RoutesFromStr(routesStr)
s4Opts := nextServerOpts(s3Opts)
// Create the routes string for s4 to connect to s3
routesStr = fmt.Sprintf("nats-route://%s:%d/", s3Opts.Cluster.Host, s3Opts.Cluster.Port)
s4Opts.Routes = server.RoutesFromStr(routesStr)
for i := 0; i < 10; i++ {
func() {
// Run these servers manually, because we want them to start and
// connect to s1 as fast as possible.
s2 := server.New(s2Opts)
if s2 == nil {
panic("No NATS Server object returned.")
}
defer s2.Shutdown()
go s2.Start()
s3 := server.New(s3Opts)
if s3 == nil {
panic("No NATS Server object returned.")
}
defer s3.Shutdown()
go s3.Start()
s4 := server.New(s4Opts)
if s4 == nil {
panic("No NATS Server object returned.")
}
defer s4.Shutdown()
go s4.Start()
serversInfo := []serverInfo{{s1, opts}, {s2, s2Opts}, {s3, s3Opts}, {s4, s4Opts}}
var err error
maxTime := time.Now().Add(5 * time.Second)
for time.Now().Before(maxTime) {
for j := 0; j < len(serversInfo); j++ {
err = checkConnected(t, serversInfo, j, false)
// If error, start this for loop from beginning
if err != nil {
// Sleep a bit before the next attempt
time.Sleep(100 * time.Millisecond)
break
}
}
// All servers checked ok, we are done, otherwise, try again
// until time is up
if err == nil {
break
}
}
// Report error
if err != nil {
t.Fatalf("Error: %v", err)
}
}()
maxTime := time.Now().Add(2 * time.Second)
for time.Now().Before(maxTime) {
if s1.NumRoutes() > 0 {
time.Sleep(10 * time.Millisecond)
} else {
break
}
}
}
}
func TestAuthSeedSolicitWorks(t *testing.T) {
s1, opts := runAuthSeedServer(t)
defer s1.Shutdown()
// Create the routes string for others to connect to the seed.
routesStr := fmt.Sprintf("nats-route://%s:%s@%s:%d/", opts.Cluster.Username, opts.Cluster.Password, opts.Cluster.Host, opts.Cluster.Port)
// Run Server #2
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s2 := RunServer(s2Opts)
defer s2.Shutdown()
// Run Server #3
s3Opts := nextServerOpts(s2Opts)
s3 := RunServer(s3Opts)
defer s3.Shutdown()
// Wait for a bit for graph to connect
time.Sleep(500 * time.Millisecond)
// Grab Routez from monitor ports, make sure we are fully connected
url := fmt.Sprintf("http://%s:%d/", opts.Host, opts.HTTPPort)
rz := readHTTPRoutez(t, url)
ris := expectRids(t, rz, []string{s2.ID(), s3.ID()})
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s2Opts.Host, s2Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s3.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s3Opts.Host, s3Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s2.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
}
// Helper to check for correct route memberships
func expectRids(t *testing.T, rz *server.Routez, rids []string) map[string]*server.RouteInfo {
ri, err := expectRidsNoFatal(t, false, rz, rids)
if err != nil {
t.Fatalf("%v", err)
}
return ri
}
func expectRidsNoFatal(t *testing.T, direct bool, rz *server.Routez, rids []string) (map[string]*server.RouteInfo, error) {
caller := 1
if !direct {
caller++
}
if len(rids) != rz.NumRoutes {
_, fn, line, _ := runtime.Caller(caller)
return nil, fmt.Errorf("[%s:%d] Expecting %d routes, got %d\n", fn, line, len(rids), rz.NumRoutes)
}
set := make(map[string]bool)
for _, v := range rids {
set[v] = true
}
// Make result map for additional checking
ri := make(map[string]*server.RouteInfo)
for _, r := range rz.Routes {
if !set[r.RemoteID] {
_, fn, line, _ := runtime.Caller(caller)
return nil, fmt.Errorf("[%s:%d] Route with rid %s unexpected, expected %+v\n", fn, line, r.RemoteID, rids)
}
ri[r.RemoteID] = r
}
return ri, nil
}
// Helper to easily grab routez info.
func readHTTPRoutez(t *testing.T, url string) *server.Routez {
resetPreviousHTTPConnections()
resp, err := http.Get(url + "routez")
if err != nil {
stackFatalf(t, "Expected no error: Got %v\n", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
stackFatalf(t, "Expected a 200 response, got %d\n", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
stackFatalf(t, "Got an error reading the body: %v\n", err)
}
r := server.Routez{}
if err := json.Unmarshal(body, &r); err != nil {
stackFatalf(t, "Got an error unmarshalling the body: %v\n", err)
}
return &r
}
func TestSeedReturnIPInInfo(t *testing.T) {
s, opts := runSeedServer(t)
defer s.Shutdown()
rc1 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc1.Close()
rc1ID := "2222"
rc1Port := 22
rc1Host := "localhost"
routeSend1, route1Expect := setupRouteEx(t, rc1, opts, rc1ID)
route1Expect(infoRe)
// register ourselves via INFO
r1Info := server.Info{ID: rc1ID, Host: rc1Host, Port: rc1Port}
b, _ := json.Marshal(r1Info)
infoJSON := fmt.Sprintf(server.InfoProto, b)
routeSend1(infoJSON)
routeSend1("PING\r\n")
route1Expect(pongRe)
rc2 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc2.Close()
rc2ID := "2224"
rc2Port := 24
rc2Host := "localhost"
routeSend2, _ := setupRouteEx(t, rc2, opts, rc2ID)
// register ourselves via INFO
r2Info := server.Info{ID: rc2ID, Host: rc2Host, Port: rc2Port}
b, _ = json.Marshal(r2Info)
infoJSON = fmt.Sprintf(server.InfoProto, b)
routeSend2(infoJSON)
// Now read info that route1 should have received from the seed
buf := route1Expect(infoRe)
info := server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.IP == "" {
t.Fatal("Expected to have IP in INFO")
}
rip, _, err := net.SplitHostPort(strings.TrimPrefix(info.IP, "nats-route://"))
if err != nil {
t.Fatalf("Error parsing url: %v", err)
}
addr, ok := rc1.RemoteAddr().(*net.TCPAddr)
if !ok {
t.Fatal("Unable to get IP address from route")
}
s1 := strings.ToLower(addr.IP.String())
s2 := strings.ToLower(rip)
if s1 != s2 {
t.Fatalf("Expected IP %s, got %s", s1, s2)
}
}
func TestImplicitRouteRetry(t *testing.T) {
srvSeed, optsSeed := runSeedServer(t)
defer srvSeed.Shutdown()
optsA := nextServerOpts(optsSeed)
optsA.Routes = server.RoutesFromStr(fmt.Sprintf("nats://%s:%d", optsSeed.Cluster.Host, optsSeed.Cluster.Port))
optsA.Cluster.ConnectRetries = 5
srvA := RunServer(optsA)
defer srvA.Shutdown()
optsB := nextServerOpts(optsA)
rcb := createRouteConn(t, optsSeed.Cluster.Host, optsSeed.Cluster.Port)
defer rcb.Close()
rcbID := "ServerB"
routeBSend, routeBExpect := setupRouteEx(t, rcb, optsB, rcbID)
routeBExpect(infoRe)
// register ourselves via INFO
rbInfo := server.Info{ID: rcbID, Host: optsB.Cluster.Host, Port: optsB.Cluster.Port}
b, _ := json.Marshal(rbInfo)
infoJSON := fmt.Sprintf(server.InfoProto, b)
routeBSend(infoJSON)
routeBSend("PING\r\n")
routeBExpect(pongRe)
// srvA should try to connect. Wait to make sure that it fails.
time.Sleep(1200 * time.Millisecond)
// Setup a fake route listen for routeB
rbListen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", optsB.Cluster.Host, optsB.Cluster.Port))
if err != nil {
t.Fatalf("Error during listen: %v", err)
}
c, err := rbListen.Accept()
if err != nil {
t.Fatalf("Error during accept: %v", err)
}
defer c.Close()
br := bufio.NewReaderSize(c, 32768)
// Consume CONNECT and INFO
for i := 0; i < 2; i++ {
c.SetReadDeadline(time.Now().Add(2 * time.Second))
buf, _, err := br.ReadLine()
c.SetReadDeadline(time.Time{})
if err != nil {
t.Fatalf("Error reading: %v", err)
}
if i == 0 {
continue
}
buf = buf[len("INFO "):]
info := &server.Info{}
if err := json.Unmarshal(buf, info); err != nil {
t.Fatalf("Error during unmarshal: %v", err)
}
// Check INFO is from server A.
if info.ID != srvA.ID() {
t.Fatalf("Expected CONNECT from %v, got CONNECT from %v", srvA.ID(), info.ID)
}
}
}