mirror of https://github.com/tidwall/tile38.git
1038 lines
31 KiB
Go
1038 lines
31 KiB
Go
// Copyright 2012-2018 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"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,
|
|
MaxPending: MAX_PENDING_SIZE,
|
|
WriteDeadline: DEFAULT_FLUSH_DEADLINE,
|
|
RQSubsSweep: DEFAULT_REMOTE_QSUBS_SWEEPER,
|
|
MaxClosedClients: DEFAULT_MAX_CLOSED_CLIENTS,
|
|
}
|
|
|
|
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: "127.0.0.1",
|
|
Port: 4242,
|
|
Username: "derek",
|
|
Password: "porkchop",
|
|
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,
|
|
MaxSubs: 1000,
|
|
MaxPending: 10000000,
|
|
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: "127.0.0.1",
|
|
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("127.0.0.1"); 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: "127.0.0.1",
|
|
Port: 2222,
|
|
Username: "derek",
|
|
Password: "porkchop",
|
|
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,
|
|
MaxSubs: 1000,
|
|
MaxPending: 10000000,
|
|
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: "porkchop",
|
|
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@127.0.0.1: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 TestDynamicPortOnListen(t *testing.T) {
|
|
opts, err := ProcessConfigFile("./configs/listen-1.conf")
|
|
if err != nil {
|
|
t.Fatalf("Received an error reading config file: %v\n", err)
|
|
}
|
|
if opts.Port != -1 {
|
|
t.Fatalf("Received incorrect port %v, expected -1\n", opts.Port)
|
|
}
|
|
if opts.HTTPPort != -1 {
|
|
t.Fatalf("Received incorrect monitoring port %v, expected -1\n", opts.HTTPPort)
|
|
}
|
|
if opts.HTTPSPort != -1 {
|
|
t.Fatalf("Received incorrect secure monitoring port %v, expected -1\n", opts.HTTPSPort)
|
|
}
|
|
}
|
|
|
|
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: "127.0.0.1",
|
|
Port: 2222,
|
|
Username: "derek",
|
|
Password: "porkchop",
|
|
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 }()
|
|
|
|
ch := make(chan bool, 1)
|
|
checkPrintInvoked := func() {
|
|
ch <- true
|
|
}
|
|
usage := func() { panic("should not get there") }
|
|
var fs *flag.FlagSet
|
|
type testPrint struct {
|
|
args []string
|
|
version, help, tlsHelp func()
|
|
}
|
|
testFuncs := []testPrint{
|
|
testPrint{[]string{"-v"}, checkPrintInvoked, usage, PrintTLSHelpAndDie},
|
|
testPrint{[]string{"version"}, checkPrintInvoked, usage, PrintTLSHelpAndDie},
|
|
testPrint{[]string{"-h"}, PrintServerAndExit, checkPrintInvoked, PrintTLSHelpAndDie},
|
|
testPrint{[]string{"help"}, PrintServerAndExit, checkPrintInvoked, PrintTLSHelpAndDie},
|
|
testPrint{[]string{"-help_tls"}, PrintServerAndExit, usage, checkPrintInvoked},
|
|
}
|
|
for _, tf := range testFuncs {
|
|
fs = flag.NewFlagSet("test", flag.ContinueOnError)
|
|
opts, err := ConfigureOptions(fs, tf.args, tf.version, tf.help, tf.tlsHelp)
|
|
if err != nil {
|
|
t.Fatalf("Error on configure: %v", err)
|
|
}
|
|
if opts != nil {
|
|
t.Fatalf("Expected options to be nil, got %v", opts)
|
|
}
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("Should have invoked print function for args=%v", tf.args)
|
|
}
|
|
}
|
|
|
|
// 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, PrintServerAndExit, fs.Usage, PrintTLSHelpAndDie)
|
|
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)
|
|
// Silence the flagSet so that on failure nothing is printed.
|
|
// (flagSet would print error message about unknown flags, etc..)
|
|
silenceOuput := &bytes.Buffer{}
|
|
fs.SetOutput(silenceOuput)
|
|
opts, err := ConfigureOptions(fs, args, PrintServerAndExit, fs.Usage, PrintTLSHelpAndDie)
|
|
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 unknown flag
|
|
expectToFail([]string{"-xxx", "foo"}, "flag")
|
|
|
|
// 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://127.0.0.1:6223")
|
|
expectToFail([]string{"-routes", expectedURL.String()}, "solicited routes")
|
|
|
|
// Ensure that we can set cluster and routes from command line
|
|
opts = mustNotFail([]string{"-cluster", "nats://127.0.0.1:6222", "-routes", expectedURL.String()})
|
|
if opts.Cluster.ListenStr != "nats://127.0.0.1:6222" {
|
|
t.Fatalf("Unexpected Cluster.ListenStr=%q", opts.Cluster.ListenStr)
|
|
}
|
|
if opts.RoutesStr != "nats://127.0.0.1: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@127.0.0.1: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://127.0.0.1", "-routes", ""}, "port")
|
|
expectToFail([]string{"-cluster", "nats://127.0.0.1:xxx", "-routes", ""}, "integer")
|
|
expectToFail([]string{"-cluster", "nats://ivan:127.0.0.1:6222", "-routes", ""}, "colons")
|
|
expectToFail([]string{"-cluster", "nats://ivan@127.0.0.1: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")
|
|
}
|
|
}
|