mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-14 18:20:42 -07:00
There were some cases where override would not work. Any command line parameter that would be set to the type default value (false for boolean, "" for string, etc) would not be taken into account. I moved all the flags parsing and options configuration into a new function, which may help reduce code duplication in NATS Streaming. The other advantage of moving this in a function is that it can now be unit tested. I am also removing call to `RemoveSelfReference()` which attempted to remove a route to self, which has been already solved at runtime with detecting and ignoring a route to self. This function would be invoked only when routes were defined in the configuration file, not in the command line parameter. Removing this call also solves an user issue (#577) Resolves #574 Resolves #577
983 lines
29 KiB
Go
983 lines
29 KiB
Go
// Copyright 2013-2016 Apcera Inc. All rights reserved.
|
|
|
|
package server
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"flag"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDefaultOptions(t *testing.T) {
|
|
golden := &Options{
|
|
Host: DEFAULT_HOST,
|
|
Port: DEFAULT_PORT,
|
|
MaxConn: DEFAULT_MAX_CONNECTIONS,
|
|
HTTPHost: DEFAULT_HOST,
|
|
PingInterval: DEFAULT_PING_INTERVAL,
|
|
MaxPingsOut: DEFAULT_PING_MAX_OUT,
|
|
TLSTimeout: float64(TLS_TIMEOUT) / float64(time.Second),
|
|
AuthTimeout: float64(AUTH_TIMEOUT) / float64(time.Second),
|
|
MaxControlLine: MAX_CONTROL_LINE_SIZE,
|
|
MaxPayload: MAX_PAYLOAD_SIZE,
|
|
Cluster: ClusterOpts{
|
|
Host: DEFAULT_HOST,
|
|
AuthTimeout: float64(AUTH_TIMEOUT) / float64(time.Second),
|
|
TLSTimeout: float64(TLS_TIMEOUT) / float64(time.Second),
|
|
},
|
|
WriteDeadline: DEFAULT_FLUSH_DEADLINE,
|
|
}
|
|
|
|
opts := &Options{}
|
|
processOptions(opts)
|
|
|
|
if !reflect.DeepEqual(golden, opts) {
|
|
t.Fatalf("Default Options are incorrect.\nexpected: %+v\ngot: %+v",
|
|
golden, opts)
|
|
}
|
|
}
|
|
|
|
func TestOptions_RandomPort(t *testing.T) {
|
|
opts := &Options{Port: RANDOM_PORT}
|
|
processOptions(opts)
|
|
|
|
if opts.Port != 0 {
|
|
t.Fatalf("Process of options should have resolved random port to "+
|
|
"zero.\nexpected: %d\ngot: %d\n", 0, opts.Port)
|
|
}
|
|
}
|
|
|
|
func TestConfigFile(t *testing.T) {
|
|
golden := &Options{
|
|
ConfigFile: "./configs/test.conf",
|
|
Host: "localhost",
|
|
Port: 4242,
|
|
Username: "derek",
|
|
Password: "bella",
|
|
AuthTimeout: 1.0,
|
|
Debug: false,
|
|
Trace: true,
|
|
Logtime: false,
|
|
HTTPPort: 8222,
|
|
PidFile: "/tmp/gnatsd.pid",
|
|
ProfPort: 6543,
|
|
Syslog: true,
|
|
RemoteSyslog: "udp://foo.com:33",
|
|
MaxControlLine: 2048,
|
|
MaxPayload: 65536,
|
|
MaxConn: 100,
|
|
PingInterval: 60 * time.Second,
|
|
MaxPingsOut: 3,
|
|
WriteDeadline: 3 * time.Second,
|
|
}
|
|
|
|
opts, err := ProcessConfigFile("./configs/test.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(golden, opts) {
|
|
t.Fatalf("Options are incorrect.\nexpected: %+v\ngot: %+v",
|
|
golden, opts)
|
|
}
|
|
}
|
|
|
|
func TestTLSConfigFile(t *testing.T) {
|
|
golden := &Options{
|
|
ConfigFile: "./configs/tls.conf",
|
|
Host: "localhost",
|
|
Port: 4443,
|
|
Username: "derek",
|
|
Password: "foo",
|
|
AuthTimeout: 1.0,
|
|
TLSTimeout: 2.0,
|
|
}
|
|
opts, err := ProcessConfigFile("./configs/tls.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
tlsConfig := opts.TLSConfig
|
|
if tlsConfig == nil {
|
|
t.Fatal("Expected opts.TLSConfig to be non-nil")
|
|
}
|
|
opts.TLSConfig = nil
|
|
if !reflect.DeepEqual(golden, opts) {
|
|
t.Fatalf("Options are incorrect.\nexpected: %+v\ngot: %+v",
|
|
golden, opts)
|
|
}
|
|
// Now check TLSConfig a bit more closely
|
|
// CipherSuites
|
|
ciphers := defaultCipherSuites()
|
|
if !reflect.DeepEqual(tlsConfig.CipherSuites, ciphers) {
|
|
t.Fatalf("Got incorrect cipher suite list: [%+v]", tlsConfig.CipherSuites)
|
|
}
|
|
if tlsConfig.MinVersion != tls.VersionTLS12 {
|
|
t.Fatalf("Expected MinVersion of 1.2 [%v], got [%v]", tls.VersionTLS12, tlsConfig.MinVersion)
|
|
}
|
|
if !tlsConfig.PreferServerCipherSuites {
|
|
t.Fatal("Expected PreferServerCipherSuites to be true")
|
|
}
|
|
// Verify hostname is correct in certificate
|
|
if len(tlsConfig.Certificates) != 1 {
|
|
t.Fatal("Expected 1 certificate")
|
|
}
|
|
cert := tlsConfig.Certificates[0].Leaf
|
|
if err := cert.VerifyHostname("localhost"); err != nil {
|
|
t.Fatalf("Could not verify hostname in certificate: %v\n", err)
|
|
}
|
|
|
|
// Now test adding cipher suites.
|
|
opts, err = ProcessConfigFile("./configs/tls_ciphers.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
tlsConfig = opts.TLSConfig
|
|
if tlsConfig == nil {
|
|
t.Fatal("Expected opts.TLSConfig to be non-nil")
|
|
}
|
|
|
|
// CipherSuites listed in the config - test all of them.
|
|
ciphers = []uint16{
|
|
tls.TLS_RSA_WITH_RC4_128_SHA,
|
|
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
}
|
|
|
|
if !reflect.DeepEqual(tlsConfig.CipherSuites, ciphers) {
|
|
t.Fatalf("Got incorrect cipher suite list: [%+v]", tlsConfig.CipherSuites)
|
|
}
|
|
|
|
// Test an unrecognized/bad cipher
|
|
if _, err := ProcessConfigFile("./configs/tls_bad_cipher.conf"); err == nil {
|
|
t.Fatal("Did not receive an error from a unrecognized cipher")
|
|
}
|
|
|
|
// Test an empty cipher entry in a config file.
|
|
if _, err := ProcessConfigFile("./configs/tls_empty_cipher.conf"); err == nil {
|
|
t.Fatal("Did not receive an error from empty cipher_suites")
|
|
}
|
|
|
|
// Test a curve preference from the config.
|
|
curves := []tls.CurveID{
|
|
tls.CurveP256,
|
|
}
|
|
|
|
// test on a file that will load the curve preference defaults
|
|
opts, err = ProcessConfigFile("./configs/tls_ciphers.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(opts.TLSConfig.CurvePreferences, defaultCurvePreferences()) {
|
|
t.Fatalf("Got incorrect curve preference list: [%+v]", tlsConfig.CurvePreferences)
|
|
}
|
|
|
|
// Test specifying a single curve preference
|
|
opts, err = ProcessConfigFile("./configs/tls_curve_prefs.conf")
|
|
if err != nil {
|
|
t.Fatal("Did not receive an error from a unrecognized cipher.")
|
|
}
|
|
|
|
if !reflect.DeepEqual(opts.TLSConfig.CurvePreferences, curves) {
|
|
t.Fatalf("Got incorrect cipher suite list: [%+v]", tlsConfig.CurvePreferences)
|
|
}
|
|
|
|
// Test an unrecognized/bad curve preference
|
|
if _, err := ProcessConfigFile("./configs/tls_bad_curve_prefs.conf"); err == nil {
|
|
t.Fatal("Did not receive an error from a unrecognized curve preference")
|
|
}
|
|
// Test an empty curve preference
|
|
if _, err := ProcessConfigFile("./configs/tls_empty_curve_prefs.conf"); err == nil {
|
|
t.Fatal("Did not receive an error from empty curve preferences")
|
|
}
|
|
}
|
|
|
|
func TestMergeOverrides(t *testing.T) {
|
|
golden := &Options{
|
|
ConfigFile: "./configs/test.conf",
|
|
Host: "localhost",
|
|
Port: 2222,
|
|
Username: "derek",
|
|
Password: "spooky",
|
|
AuthTimeout: 1.0,
|
|
Debug: true,
|
|
Trace: true,
|
|
Logtime: false,
|
|
HTTPPort: DEFAULT_HTTP_PORT,
|
|
PidFile: "/tmp/gnatsd.pid",
|
|
ProfPort: 6789,
|
|
Syslog: true,
|
|
RemoteSyslog: "udp://foo.com:33",
|
|
MaxControlLine: 2048,
|
|
MaxPayload: 65536,
|
|
MaxConn: 100,
|
|
PingInterval: 60 * time.Second,
|
|
MaxPingsOut: 3,
|
|
Cluster: ClusterOpts{
|
|
NoAdvertise: true,
|
|
ConnectRetries: 2,
|
|
},
|
|
WriteDeadline: 3 * time.Second,
|
|
}
|
|
fopts, err := ProcessConfigFile("./configs/test.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
|
|
// Overrides via flags
|
|
opts := &Options{
|
|
Port: 2222,
|
|
Password: "spooky",
|
|
Debug: true,
|
|
HTTPPort: DEFAULT_HTTP_PORT,
|
|
ProfPort: 6789,
|
|
Cluster: ClusterOpts{
|
|
NoAdvertise: true,
|
|
ConnectRetries: 2,
|
|
},
|
|
}
|
|
merged := MergeOptions(fopts, opts)
|
|
|
|
if !reflect.DeepEqual(golden, merged) {
|
|
t.Fatalf("Options are incorrect.\nexpected: %+v\ngot: %+v",
|
|
golden, merged)
|
|
}
|
|
}
|
|
|
|
func TestRemoveSelfReference(t *testing.T) {
|
|
url1, _ := url.Parse("nats-route://user:password@10.4.5.6:4223")
|
|
url2, _ := url.Parse("nats-route://user:password@localhost:4223")
|
|
url3, _ := url.Parse("nats-route://user:password@127.0.0.1:4223")
|
|
|
|
routes := []*url.URL{url1, url2, url3}
|
|
|
|
newroutes, err := RemoveSelfReference(4223, routes)
|
|
if err != nil {
|
|
t.Fatalf("Error during RemoveSelfReference: %v", err)
|
|
}
|
|
|
|
if len(newroutes) != 1 {
|
|
t.Fatalf("Wrong number of routes: %d", len(newroutes))
|
|
}
|
|
|
|
if newroutes[0] != routes[0] {
|
|
t.Fatalf("Self reference IP address %s in Routes", routes[0])
|
|
}
|
|
}
|
|
|
|
func TestAllowRouteWithDifferentPort(t *testing.T) {
|
|
url1, _ := url.Parse("nats-route://user:password@127.0.0.1:4224")
|
|
routes := []*url.URL{url1}
|
|
|
|
newroutes, err := RemoveSelfReference(4223, routes)
|
|
if err != nil {
|
|
t.Fatalf("Error during RemoveSelfReference: %v", err)
|
|
}
|
|
|
|
if len(newroutes) != 1 {
|
|
t.Fatalf("Wrong number of routes: %d", len(newroutes))
|
|
}
|
|
}
|
|
|
|
func TestRouteFlagOverride(t *testing.T) {
|
|
routeFlag := "nats-route://ruser:top_secret@127.0.0.1:8246"
|
|
rurl, _ := url.Parse(routeFlag)
|
|
|
|
golden := &Options{
|
|
ConfigFile: "./configs/srv_a.conf",
|
|
Host: "127.0.0.1",
|
|
Port: 7222,
|
|
Cluster: ClusterOpts{
|
|
Host: "127.0.0.1",
|
|
Port: 7244,
|
|
Username: "ruser",
|
|
Password: "top_secret",
|
|
AuthTimeout: 0.5,
|
|
},
|
|
Routes: []*url.URL{rurl},
|
|
RoutesStr: routeFlag,
|
|
}
|
|
|
|
fopts, err := ProcessConfigFile("./configs/srv_a.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
|
|
// Overrides via flags
|
|
opts := &Options{
|
|
RoutesStr: routeFlag,
|
|
}
|
|
merged := MergeOptions(fopts, opts)
|
|
|
|
if !reflect.DeepEqual(golden, merged) {
|
|
t.Fatalf("Options are incorrect.\nexpected: %+v\ngot: %+v",
|
|
golden, merged)
|
|
}
|
|
}
|
|
|
|
func TestClusterFlagsOverride(t *testing.T) {
|
|
routeFlag := "nats-route://ruser:top_secret@127.0.0.1:7246"
|
|
rurl, _ := url.Parse(routeFlag)
|
|
|
|
// In this test, we override the cluster listen string. Note that in
|
|
// the golden options, the cluster other infos correspond to what
|
|
// is recovered from the configuration file, this explains the
|
|
// discrepency between ClusterListenStr and the rest.
|
|
// The server would then process the ClusterListenStr override and
|
|
// correctly override ClusterHost/ClustherPort/etc..
|
|
golden := &Options{
|
|
ConfigFile: "./configs/srv_a.conf",
|
|
Host: "127.0.0.1",
|
|
Port: 7222,
|
|
Cluster: ClusterOpts{
|
|
Host: "127.0.0.1",
|
|
Port: 7244,
|
|
ListenStr: "nats://127.0.0.1:8224",
|
|
Username: "ruser",
|
|
Password: "top_secret",
|
|
AuthTimeout: 0.5,
|
|
},
|
|
Routes: []*url.URL{rurl},
|
|
}
|
|
|
|
fopts, err := ProcessConfigFile("./configs/srv_a.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
|
|
// Overrides via flags
|
|
opts := &Options{
|
|
Cluster: ClusterOpts{
|
|
ListenStr: "nats://127.0.0.1:8224",
|
|
},
|
|
}
|
|
merged := MergeOptions(fopts, opts)
|
|
|
|
if !reflect.DeepEqual(golden, merged) {
|
|
t.Fatalf("Options are incorrect.\nexpected: %+v\ngot: %+v",
|
|
golden, merged)
|
|
}
|
|
}
|
|
|
|
func TestRouteFlagOverrideWithMultiple(t *testing.T) {
|
|
routeFlag := "nats-route://ruser:top_secret@127.0.0.1:8246, nats-route://ruser:top_secret@127.0.0.1:8266"
|
|
rurls := RoutesFromStr(routeFlag)
|
|
|
|
golden := &Options{
|
|
ConfigFile: "./configs/srv_a.conf",
|
|
Host: "127.0.0.1",
|
|
Port: 7222,
|
|
Cluster: ClusterOpts{
|
|
Host: "127.0.0.1",
|
|
Port: 7244,
|
|
Username: "ruser",
|
|
Password: "top_secret",
|
|
AuthTimeout: 0.5,
|
|
},
|
|
Routes: rurls,
|
|
RoutesStr: routeFlag,
|
|
}
|
|
|
|
fopts, err := ProcessConfigFile("./configs/srv_a.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
|
|
// Overrides via flags
|
|
opts := &Options{
|
|
RoutesStr: routeFlag,
|
|
}
|
|
merged := MergeOptions(fopts, opts)
|
|
|
|
if !reflect.DeepEqual(golden, merged) {
|
|
t.Fatalf("Options are incorrect.\nexpected: %+v\ngot: %+v",
|
|
golden, merged)
|
|
}
|
|
}
|
|
|
|
func TestListenConfig(t *testing.T) {
|
|
opts, err := ProcessConfigFile("./configs/listen.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
processOptions(opts)
|
|
|
|
// Normal clients
|
|
host := "10.0.1.22"
|
|
port := 4422
|
|
monHost := "127.0.0.1"
|
|
if opts.Host != host {
|
|
t.Fatalf("Received incorrect host %q, expected %q\n", opts.Host, host)
|
|
}
|
|
if opts.HTTPHost != monHost {
|
|
t.Fatalf("Received incorrect host %q, expected %q\n", opts.HTTPHost, monHost)
|
|
}
|
|
if opts.Port != port {
|
|
t.Fatalf("Received incorrect port %v, expected %v\n", opts.Port, port)
|
|
}
|
|
|
|
// Clustering
|
|
clusterHost := "127.0.0.1"
|
|
clusterPort := 4244
|
|
|
|
if opts.Cluster.Host != clusterHost {
|
|
t.Fatalf("Received incorrect cluster host %q, expected %q\n", opts.Cluster.Host, clusterHost)
|
|
}
|
|
if opts.Cluster.Port != clusterPort {
|
|
t.Fatalf("Received incorrect cluster port %v, expected %v\n", opts.Cluster.Port, clusterPort)
|
|
}
|
|
|
|
// HTTP
|
|
httpHost := "127.0.0.1"
|
|
httpPort := 8422
|
|
|
|
if opts.HTTPHost != httpHost {
|
|
t.Fatalf("Received incorrect http host %q, expected %q\n", opts.HTTPHost, httpHost)
|
|
}
|
|
if opts.HTTPPort != httpPort {
|
|
t.Fatalf("Received incorrect http port %v, expected %v\n", opts.HTTPPort, httpPort)
|
|
}
|
|
|
|
// HTTPS
|
|
httpsPort := 9443
|
|
if opts.HTTPSPort != httpsPort {
|
|
t.Fatalf("Received incorrect https port %v, expected %v\n", opts.HTTPSPort, httpsPort)
|
|
}
|
|
}
|
|
|
|
func TestListenPortOnlyConfig(t *testing.T) {
|
|
opts, err := ProcessConfigFile("./configs/listen_port.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
processOptions(opts)
|
|
|
|
port := 8922
|
|
|
|
if opts.Host != DEFAULT_HOST {
|
|
t.Fatalf("Received incorrect host %q, expected %q\n", opts.Host, DEFAULT_HOST)
|
|
}
|
|
if opts.HTTPHost != DEFAULT_HOST {
|
|
t.Fatalf("Received incorrect host %q, expected %q\n", opts.Host, DEFAULT_HOST)
|
|
}
|
|
if opts.Port != port {
|
|
t.Fatalf("Received incorrect port %v, expected %v\n", opts.Port, port)
|
|
}
|
|
}
|
|
|
|
func TestListenPortWithColonConfig(t *testing.T) {
|
|
opts, err := ProcessConfigFile("./configs/listen_port_with_colon.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
processOptions(opts)
|
|
|
|
port := 8922
|
|
|
|
if opts.Host != DEFAULT_HOST {
|
|
t.Fatalf("Received incorrect host %q, expected %q\n", opts.Host, DEFAULT_HOST)
|
|
}
|
|
if opts.HTTPHost != DEFAULT_HOST {
|
|
t.Fatalf("Received incorrect host %q, expected %q\n", opts.Host, DEFAULT_HOST)
|
|
}
|
|
if opts.Port != port {
|
|
t.Fatalf("Received incorrect port %v, expected %v\n", opts.Port, port)
|
|
}
|
|
}
|
|
|
|
func TestListenMonitoringDefault(t *testing.T) {
|
|
opts := &Options{
|
|
Host: "10.0.1.22",
|
|
}
|
|
processOptions(opts)
|
|
|
|
host := "10.0.1.22"
|
|
if opts.Host != host {
|
|
t.Fatalf("Received incorrect host %q, expected %q\n", opts.Host, host)
|
|
}
|
|
if opts.HTTPHost != host {
|
|
t.Fatalf("Received incorrect host %q, expected %q\n", opts.Host, host)
|
|
}
|
|
if opts.Port != DEFAULT_PORT {
|
|
t.Fatalf("Received incorrect port %v, expected %v\n", opts.Port, DEFAULT_PORT)
|
|
}
|
|
}
|
|
|
|
func TestMultipleUsersConfig(t *testing.T) {
|
|
opts, err := ProcessConfigFile("./configs/multiple_users.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
processOptions(opts)
|
|
}
|
|
|
|
// Test highly depends on contents of the config file listed below. Any changes to that file
|
|
// may very well break this test.
|
|
func TestAuthorizationConfig(t *testing.T) {
|
|
opts, err := ProcessConfigFile("./configs/authorization.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
processOptions(opts)
|
|
lu := len(opts.Users)
|
|
if lu != 3 {
|
|
t.Fatalf("Expected 3 users, got %d\n", lu)
|
|
}
|
|
// Build a map
|
|
mu := make(map[string]*User)
|
|
for _, u := range opts.Users {
|
|
mu[u.Username] = u
|
|
}
|
|
|
|
// Alice
|
|
alice, ok := mu["alice"]
|
|
if !ok {
|
|
t.Fatalf("Expected to see user Alice\n")
|
|
}
|
|
// Check for permissions details
|
|
if alice.Permissions == nil {
|
|
t.Fatalf("Expected Alice's permissions to be non-nil\n")
|
|
}
|
|
if alice.Permissions.Publish == nil {
|
|
t.Fatalf("Expected Alice's publish permissions to be non-nil\n")
|
|
}
|
|
if len(alice.Permissions.Publish) != 1 {
|
|
t.Fatalf("Expected Alice's publish permissions to have 1 element, got %d\n",
|
|
len(alice.Permissions.Publish))
|
|
}
|
|
pubPerm := alice.Permissions.Publish[0]
|
|
if pubPerm != "*" {
|
|
t.Fatalf("Expected Alice's publish permissions to be '*', got %q\n", pubPerm)
|
|
}
|
|
if alice.Permissions.Subscribe == nil {
|
|
t.Fatalf("Expected Alice's subscribe permissions to be non-nil\n")
|
|
}
|
|
if len(alice.Permissions.Subscribe) != 1 {
|
|
t.Fatalf("Expected Alice's subscribe permissions to have 1 element, got %d\n",
|
|
len(alice.Permissions.Subscribe))
|
|
}
|
|
subPerm := alice.Permissions.Subscribe[0]
|
|
if subPerm != ">" {
|
|
t.Fatalf("Expected Alice's subscribe permissions to be '>', got %q\n", subPerm)
|
|
}
|
|
|
|
// Bob
|
|
bob, ok := mu["bob"]
|
|
if !ok {
|
|
t.Fatalf("Expected to see user Bob\n")
|
|
}
|
|
if bob.Permissions == nil {
|
|
t.Fatalf("Expected Bob's permissions to be non-nil\n")
|
|
}
|
|
|
|
// Susan
|
|
susan, ok := mu["susan"]
|
|
if !ok {
|
|
t.Fatalf("Expected to see user Susan\n")
|
|
}
|
|
if susan.Permissions == nil {
|
|
t.Fatalf("Expected Susan's permissions to be non-nil\n")
|
|
}
|
|
// Check susan closely since she inherited the default permissions.
|
|
if susan.Permissions == nil {
|
|
t.Fatalf("Expected Susan's permissions to be non-nil\n")
|
|
}
|
|
if susan.Permissions.Publish != nil {
|
|
t.Fatalf("Expected Susan's publish permissions to be nil\n")
|
|
}
|
|
if susan.Permissions.Subscribe == nil {
|
|
t.Fatalf("Expected Susan's subscribe permissions to be non-nil\n")
|
|
}
|
|
if len(susan.Permissions.Subscribe) != 1 {
|
|
t.Fatalf("Expected Susan's subscribe permissions to have 1 element, got %d\n",
|
|
len(susan.Permissions.Subscribe))
|
|
}
|
|
subPerm = susan.Permissions.Subscribe[0]
|
|
if subPerm != "PUBLIC.>" {
|
|
t.Fatalf("Expected Susan's subscribe permissions to be 'PUBLIC.>', got %q\n", subPerm)
|
|
}
|
|
}
|
|
|
|
func TestTokenWithUserPass(t *testing.T) {
|
|
confFileName := "test.conf"
|
|
defer os.Remove(confFileName)
|
|
content := `
|
|
authorization={
|
|
user: user
|
|
pass: password
|
|
token: $2a$11$whatever
|
|
}`
|
|
if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil {
|
|
t.Fatalf("Error writing config file: %v", err)
|
|
}
|
|
_, err := ProcessConfigFile(confFileName)
|
|
if err == nil {
|
|
t.Fatal("Expected error, got none")
|
|
}
|
|
if !strings.Contains(err.Error(), "token") {
|
|
t.Fatalf("Expected error related to token, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTokenWithUsers(t *testing.T) {
|
|
confFileName := "test.conf"
|
|
defer os.Remove(confFileName)
|
|
content := `
|
|
authorization={
|
|
token: $2a$11$whatever
|
|
users: [
|
|
{user: test, password: test}
|
|
]
|
|
}`
|
|
if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil {
|
|
t.Fatalf("Error writing config file: %v", err)
|
|
}
|
|
_, err := ProcessConfigFile(confFileName)
|
|
if err == nil {
|
|
t.Fatal("Expected error, got none")
|
|
}
|
|
if !strings.Contains(err.Error(), "token") {
|
|
t.Fatalf("Expected error related to token, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseWriteDeadline(t *testing.T) {
|
|
confFile := "test.conf"
|
|
defer os.Remove(confFile)
|
|
if err := ioutil.WriteFile(confFile, []byte("write_deadline: \"1x\"\n"), 0666); err != nil {
|
|
t.Fatalf("Error writing config file: %v", err)
|
|
}
|
|
_, err := ProcessConfigFile(confFile)
|
|
if err == nil {
|
|
t.Fatal("Expected error, got none")
|
|
}
|
|
if !strings.Contains(err.Error(), "parsing") {
|
|
t.Fatalf("Expected error related to parsing, got %v", err)
|
|
}
|
|
os.Remove(confFile)
|
|
if err := ioutil.WriteFile(confFile, []byte("write_deadline: \"1s\"\n"), 0666); err != nil {
|
|
t.Fatalf("Error writing config file: %v", err)
|
|
}
|
|
opts, err := ProcessConfigFile(confFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if opts.WriteDeadline != time.Second {
|
|
t.Fatalf("Expected write_deadline to be 1s, got %v", opts.WriteDeadline)
|
|
}
|
|
os.Remove(confFile)
|
|
oldStdout := os.Stdout
|
|
_, w, _ := os.Pipe()
|
|
defer func() {
|
|
w.Close()
|
|
os.Stdout = oldStdout
|
|
}()
|
|
os.Stdout = w
|
|
if err := ioutil.WriteFile(confFile, []byte("write_deadline: 2\n"), 0666); err != nil {
|
|
t.Fatalf("Error writing config file: %v", err)
|
|
}
|
|
opts, err = ProcessConfigFile(confFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if opts.WriteDeadline != 2*time.Second {
|
|
t.Fatalf("Expected write_deadline to be 2s, got %v", opts.WriteDeadline)
|
|
}
|
|
}
|
|
|
|
func TestOptionsClone(t *testing.T) {
|
|
opts := &Options{
|
|
ConfigFile: "./configs/test.conf",
|
|
Host: "localhost",
|
|
Port: 2222,
|
|
Username: "derek",
|
|
Password: "spooky",
|
|
AuthTimeout: 1.0,
|
|
Debug: true,
|
|
Trace: true,
|
|
Logtime: false,
|
|
HTTPPort: DEFAULT_HTTP_PORT,
|
|
PidFile: "/tmp/gnatsd.pid",
|
|
ProfPort: 6789,
|
|
Syslog: true,
|
|
RemoteSyslog: "udp://foo.com:33",
|
|
MaxControlLine: 2048,
|
|
MaxPayload: 65536,
|
|
MaxConn: 100,
|
|
PingInterval: 60 * time.Second,
|
|
MaxPingsOut: 3,
|
|
Cluster: ClusterOpts{
|
|
NoAdvertise: true,
|
|
ConnectRetries: 2,
|
|
},
|
|
WriteDeadline: 3 * time.Second,
|
|
Routes: []*url.URL{&url.URL{}},
|
|
Users: []*User{&User{Username: "foo", Password: "bar"}},
|
|
}
|
|
|
|
clone := opts.Clone()
|
|
|
|
if !reflect.DeepEqual(opts, clone) {
|
|
t.Fatalf("Cloned Options are incorrect.\nexpected: %+v\ngot: %+v",
|
|
clone, opts)
|
|
}
|
|
|
|
clone.Users[0].Password = "baz"
|
|
if reflect.DeepEqual(opts, clone) {
|
|
t.Fatal("Expected Options to be different")
|
|
}
|
|
}
|
|
|
|
func TestOptionsCloneNilLists(t *testing.T) {
|
|
opts := &Options{}
|
|
|
|
clone := opts.Clone()
|
|
|
|
if clone.Routes != nil {
|
|
t.Fatalf("Expected Routes to be nil, got: %v", clone.Routes)
|
|
}
|
|
if clone.Users != nil {
|
|
t.Fatalf("Expected Users to be nil, got: %v", clone.Users)
|
|
}
|
|
}
|
|
|
|
func TestOptionsCloneNil(t *testing.T) {
|
|
opts := (*Options)(nil)
|
|
clone := opts.Clone()
|
|
if clone != nil {
|
|
t.Fatalf("Expected nil, got: %+v", clone)
|
|
}
|
|
}
|
|
|
|
func TestEmptyConfig(t *testing.T) {
|
|
opts, err := ProcessConfigFile("")
|
|
|
|
if err != nil {
|
|
t.Fatalf("Expected no error from empty config, got: %+v", err)
|
|
}
|
|
|
|
if opts.ConfigFile != "" {
|
|
t.Fatalf("Expected empty config, got: %+v", opts)
|
|
}
|
|
}
|
|
|
|
func TestMalformedListenAddress(t *testing.T) {
|
|
opts, err := ProcessConfigFile("./configs/malformed_listen_address.conf")
|
|
if err == nil {
|
|
t.Fatalf("Expected an error reading config file: got %+v\n", opts)
|
|
}
|
|
}
|
|
|
|
func TestMalformedClusterAddress(t *testing.T) {
|
|
opts, err := ProcessConfigFile("./configs/malformed_cluster_address.conf")
|
|
if err == nil {
|
|
t.Fatalf("Expected an error reading config file: got %+v\n", opts)
|
|
}
|
|
}
|
|
|
|
func TestOptionsProcessConfigFile(t *testing.T) {
|
|
// Create options with default values of Debug and Trace
|
|
// that are the opposite of what is in the config file.
|
|
// Set another option that is not present in the config file.
|
|
logFileName := "test.log"
|
|
opts := &Options{
|
|
Debug: true,
|
|
Trace: false,
|
|
LogFile: logFileName,
|
|
}
|
|
configFileName := "./configs/test.conf"
|
|
if err := opts.ProcessConfigFile(configFileName); err != nil {
|
|
t.Fatalf("Error processing config file: %v", err)
|
|
}
|
|
// Verify that values are as expected
|
|
if opts.ConfigFile != configFileName {
|
|
t.Fatalf("Expected ConfigFile to be set to %q, got %v", configFileName, opts.ConfigFile)
|
|
}
|
|
if opts.Debug {
|
|
t.Fatal("Debug option should have been set to false from config file")
|
|
}
|
|
if !opts.Trace {
|
|
t.Fatal("Trace option should have been set to true from config file")
|
|
}
|
|
if opts.LogFile != logFileName {
|
|
t.Fatalf("Expected LogFile to be %q, got %q", logFileName, opts.LogFile)
|
|
}
|
|
}
|
|
|
|
func TestConfigureOptions(t *testing.T) {
|
|
// Options.Configure() will snapshot the flags. This is used by the reload code.
|
|
// We need to set it back to nil otherwise it will impact reload tests.
|
|
defer func() { FlagSnapshot = nil }()
|
|
|
|
// Ensure that Usage is called when param "help" is set
|
|
fs := flag.NewFlagSet("test", flag.ContinueOnError)
|
|
ch := make(chan bool, 1)
|
|
fs.Usage = func() {
|
|
ch <- true
|
|
}
|
|
if _, err := ConfigureOptions(fs, []string{"help"}); err != nil {
|
|
t.Fatalf("Error on configure: %v", err)
|
|
}
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Should have invoked flag set's Usage")
|
|
}
|
|
|
|
// Helper function that expect parsing with given args to not produce an error.
|
|
mustNotFail := func(args []string) *Options {
|
|
fs := flag.NewFlagSet("test", flag.ContinueOnError)
|
|
opts, err := ConfigureOptions(fs, args)
|
|
if err != nil {
|
|
stackFatalf(t, "Error on configure: %v", err)
|
|
}
|
|
return opts
|
|
}
|
|
|
|
// Helper function that expect configuration to fail.
|
|
expectToFail := func(args []string, errContent ...string) {
|
|
fs := flag.NewFlagSet("test", flag.ContinueOnError)
|
|
opts, err := ConfigureOptions(fs, args)
|
|
if opts != nil || err == nil {
|
|
stackFatalf(t, "Expected no option and an error, got opts=%v and err=%v", opts, err)
|
|
}
|
|
for _, testErr := range errContent {
|
|
if strings.Contains(err.Error(), testErr) {
|
|
// We got the error we wanted.
|
|
return
|
|
}
|
|
}
|
|
stackFatalf(t, "Expected errors containing any of those %v, got %v", errContent, err)
|
|
}
|
|
|
|
// Basic test with port number
|
|
opts := mustNotFail([]string{"-p", "1234"})
|
|
if opts.Port != 1234 {
|
|
t.Fatalf("Expected port to be 1234, got %v", opts.Port)
|
|
}
|
|
|
|
// Should fail because of unknown parameter
|
|
expectToFail([]string{"foo"}, "command")
|
|
|
|
// Should fail because of config file missing
|
|
expectToFail([]string{"-c", "xxx.cfg"}, "file")
|
|
|
|
// Should fail because of too many args for signal command
|
|
expectToFail([]string{"-sl", "quit=pid=foo"}, "signal")
|
|
|
|
// Should fail because of invalid pid
|
|
// On windows, if not running with admin privileges, you would get access denied.
|
|
expectToFail([]string{"-sl", "quit=pid"}, "pid", "denied")
|
|
|
|
// The config file set Trace to true.
|
|
opts = mustNotFail([]string{"-c", "./configs/test.conf"})
|
|
if !opts.Trace {
|
|
t.Fatal("Trace should have been set to true")
|
|
}
|
|
|
|
// The config file set Trace to true, but was overridden by param -V=false
|
|
opts = mustNotFail([]string{"-c", "./configs/test.conf", "-V=false"})
|
|
if opts.Trace {
|
|
t.Fatal("Trace should have been set to false")
|
|
}
|
|
|
|
// The config file set Trace to true, but was overridden by param -DV=false
|
|
opts = mustNotFail([]string{"-c", "./configs/test.conf", "-DV=false"})
|
|
if opts.Debug || opts.Trace {
|
|
t.Fatal("Debug and Trace should have been set to false")
|
|
}
|
|
|
|
// The config file set Trace to true, but was overridden by param -DV
|
|
opts = mustNotFail([]string{"-c", "./configs/test.conf", "-DV"})
|
|
if !opts.Debug || !opts.Trace {
|
|
t.Fatal("Debug and Trace should have been set to true")
|
|
}
|
|
|
|
// This should fail since -cluster is missing
|
|
expectedURL, _ := url.Parse("nats://localhost:6223")
|
|
expectToFail([]string{"-routes", expectedURL.String()}, "solicited routes")
|
|
|
|
// Ensure that we can set cluster and routes from command line
|
|
opts = mustNotFail([]string{"-cluster", "nats://localhost:6222", "-routes", expectedURL.String()})
|
|
if opts.Cluster.ListenStr != "nats://localhost:6222" {
|
|
t.Fatalf("Unexpected Cluster.ListenStr=%q", opts.Cluster.ListenStr)
|
|
}
|
|
if opts.RoutesStr != "nats://localhost:6223" || len(opts.Routes) != 1 || opts.Routes[0].String() != expectedURL.String() {
|
|
t.Fatalf("Unexpected RoutesStr: %q and Routes: %v", opts.RoutesStr, opts.Routes)
|
|
}
|
|
|
|
// Use a config with cluster configuration and explicit route defined.
|
|
// Override with empty routes string.
|
|
opts = mustNotFail([]string{"-c", "./configs/srv_a.conf", "-routes", ""})
|
|
if opts.RoutesStr != "" || len(opts.Routes) != 0 {
|
|
t.Fatalf("Unexpected RoutesStr: %q and Routes: %v", opts.RoutesStr, opts.Routes)
|
|
}
|
|
|
|
// Use a config with cluster configuration and override cluster listen string
|
|
expectedURL, _ = url.Parse("nats-route://ruser:top_secret@127.0.0.1:7246")
|
|
opts = mustNotFail([]string{"-c", "./configs/srv_a.conf", "-cluster", "nats://ivan:pwd@localhost:6222"})
|
|
if opts.Cluster.Username != "ivan" || opts.Cluster.Password != "pwd" || opts.Cluster.Port != 6222 ||
|
|
len(opts.Routes) != 1 || opts.Routes[0].String() != expectedURL.String() {
|
|
t.Fatalf("Unexpected Cluster and/or Routes: %#v - %v", opts.Cluster, opts.Routes)
|
|
}
|
|
|
|
// Disable clustering from command line
|
|
opts = mustNotFail([]string{"-c", "./configs/srv_a.conf", "-cluster", ""})
|
|
if opts.Cluster.Port != 0 {
|
|
t.Fatalf("Unexpected Cluster: %v", opts.Cluster)
|
|
}
|
|
|
|
// Various erros due to malformed cluster listen string.
|
|
// (adding -routes to have more than 1 set flag to check
|
|
// that Visit() stops when an error is found).
|
|
expectToFail([]string{"-cluster", ":", "-routes", ""}, "protocol")
|
|
expectToFail([]string{"-cluster", "nats://localhost", "-routes", ""}, "port")
|
|
expectToFail([]string{"-cluster", "nats://localhost:xxx", "-routes", ""}, "integer")
|
|
expectToFail([]string{"-cluster", "nats://ivan:localhost:6222", "-routes", ""}, "colons")
|
|
expectToFail([]string{"-cluster", "nats://ivan@localhost:6222", "-routes", ""}, "password")
|
|
|
|
// Override config file's TLS configuration from command line, and completely disable TLS
|
|
opts = mustNotFail([]string{"-c", "./configs/tls.conf", "-tls=false"})
|
|
if opts.TLSConfig != nil || opts.TLS {
|
|
t.Fatal("Expected TLS to be disabled")
|
|
}
|
|
// Override config file's TLS configuration from command line, and force TLS verification.
|
|
// However, since TLS config has to be regenerated, user need to provide -tlscert and -tlskey too.
|
|
// So this should fail.
|
|
expectToFail([]string{"-c", "./configs/tls.conf", "-tlsverify"}, "valid")
|
|
|
|
// Now same than above, but with all valid params.
|
|
opts = mustNotFail([]string{"-c", "./configs/tls.conf", "-tlsverify", "-tlscert", "./configs/certs/server.pem", "-tlskey", "./configs/certs/key.pem"})
|
|
if opts.TLSConfig == nil || !opts.TLSVerify {
|
|
t.Fatal("Expected TLS to be configured and force verification")
|
|
}
|
|
|
|
// Configure TLS, but some TLS params missing
|
|
expectToFail([]string{"-tls"}, "valid")
|
|
expectToFail([]string{"-tls", "-tlscert", "./configs/certs/server.pem"}, "valid")
|
|
// One of the file does not exist
|
|
expectToFail([]string{"-tls", "-tlscert", "./configs/certs/server.pem", "-tlskey", "./configs/certs/notfound.pem"}, "file")
|
|
|
|
// Configure TLS and check that this results in a TLSConfig option.
|
|
opts = mustNotFail([]string{"-tls", "-tlscert", "./configs/certs/server.pem", "-tlskey", "./configs/certs/key.pem"})
|
|
if opts.TLSConfig == nil || !opts.TLS {
|
|
t.Fatal("Expected TLSConfig to be set")
|
|
}
|
|
}
|