f0c3dca49c
Copy the updated version of freeport (sdk/freeport), and tweak it for use in Nomad tests. This means staying below port 10000 to avoid conflicts with the lib/freeport that is still transitively used by the old version of consul that we vendor. Also provide implementations to find ephemeral ports of macOS and Windows environments. Ports acquired through freeport are supposed to be returned to freeport, which this change now also introduces. Many tests are modified to include calls to a cleanup function for Server objects. This should help quite a bit with some flakey tests, but not all of them. Our port problems will not go away completely until we upgrade our vendor version of consul. With Go modules, we'll probably do a 'replace' to swap out other copies of freeport with the one now in 'nomad/helper/freeport'.
1133 lines
31 KiB
Go
1133 lines
31 KiB
Go
package agent
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/nomad/client/testutil"
|
|
"github.com/hashicorp/nomad/helper"
|
|
"github.com/hashicorp/nomad/helper/freeport"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/nomad/structs/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
// trueValue/falseValue are used to get a pointer to a boolean
|
|
trueValue = true
|
|
falseValue = false
|
|
)
|
|
|
|
func TestConfig_Merge(t *testing.T) {
|
|
c0 := &Config{}
|
|
|
|
c1 := &Config{
|
|
Telemetry: &Telemetry{},
|
|
Client: &ClientConfig{},
|
|
Server: &ServerConfig{},
|
|
ACL: &ACLConfig{},
|
|
Ports: &Ports{},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{},
|
|
Vault: &config.VaultConfig{},
|
|
Consul: &config.ConsulConfig{},
|
|
Sentinel: &config.SentinelConfig{},
|
|
Autopilot: &config.AutopilotConfig{},
|
|
}
|
|
|
|
c2 := &Config{
|
|
Region: "global",
|
|
Datacenter: "dc1",
|
|
NodeName: "node1",
|
|
DataDir: "/tmp/dir1",
|
|
PluginDir: "/tmp/pluginDir1",
|
|
LogLevel: "INFO",
|
|
LogJson: false,
|
|
EnableDebug: false,
|
|
LeaveOnInt: false,
|
|
LeaveOnTerm: false,
|
|
EnableSyslog: false,
|
|
SyslogFacility: "local0.info",
|
|
DisableUpdateCheck: helper.BoolToPtr(false),
|
|
DisableAnonymousSignature: false,
|
|
BindAddr: "127.0.0.1",
|
|
Telemetry: &Telemetry{
|
|
StatsiteAddr: "127.0.0.1:8125",
|
|
StatsdAddr: "127.0.0.1:8125",
|
|
DataDogAddr: "127.0.0.1:8125",
|
|
DataDogTags: []string{"cat1:tag1", "cat2:tag2"},
|
|
PrometheusMetrics: true,
|
|
DisableHostname: false,
|
|
DisableTaggedMetrics: true,
|
|
BackwardsCompatibleMetrics: true,
|
|
CirconusAPIToken: "0",
|
|
CirconusAPIApp: "nomadic",
|
|
CirconusAPIURL: "http://api.circonus.com/v2",
|
|
CirconusSubmissionInterval: "60s",
|
|
CirconusCheckSubmissionURL: "https://someplace.com/metrics",
|
|
CirconusCheckID: "0",
|
|
CirconusCheckForceMetricActivation: "true",
|
|
CirconusCheckInstanceID: "node1:nomadic",
|
|
CirconusCheckSearchTag: "service:nomadic",
|
|
CirconusCheckDisplayName: "node1:nomadic",
|
|
CirconusCheckTags: "cat1:tag1,cat2:tag2",
|
|
CirconusBrokerID: "0",
|
|
CirconusBrokerSelectTag: "dc:dc1",
|
|
PrefixFilter: []string{"filter1", "filter2"},
|
|
},
|
|
Client: &ClientConfig{
|
|
Enabled: false,
|
|
StateDir: "/tmp/state1",
|
|
AllocDir: "/tmp/alloc1",
|
|
NodeClass: "class1",
|
|
Options: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
NetworkSpeed: 100,
|
|
CpuCompute: 100,
|
|
MemoryMB: 100,
|
|
MaxKillTimeout: "20s",
|
|
ClientMaxPort: 19996,
|
|
DisableRemoteExec: false,
|
|
TemplateConfig: &ClientTemplateConfig{
|
|
FunctionBlacklist: []string{"plugin"},
|
|
DisableSandbox: false,
|
|
},
|
|
Reserved: &Resources{
|
|
CPU: 10,
|
|
MemoryMB: 10,
|
|
DiskMB: 10,
|
|
ReservedPorts: "1,10-30,55",
|
|
},
|
|
},
|
|
Server: &ServerConfig{
|
|
Enabled: false,
|
|
AuthoritativeRegion: "global",
|
|
BootstrapExpect: 1,
|
|
DataDir: "/tmp/data1",
|
|
ProtocolVersion: 1,
|
|
RaftProtocol: 1,
|
|
NumSchedulers: helper.IntToPtr(1),
|
|
NodeGCThreshold: "1h",
|
|
HeartbeatGrace: 30 * time.Second,
|
|
MinHeartbeatTTL: 30 * time.Second,
|
|
MaxHeartbeatsPerSecond: 30.0,
|
|
RedundancyZone: "foo",
|
|
UpgradeVersion: "foo",
|
|
},
|
|
ACL: &ACLConfig{
|
|
Enabled: true,
|
|
TokenTTL: 60 * time.Second,
|
|
PolicyTTL: 60 * time.Second,
|
|
ReplicationToken: "foo",
|
|
},
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{
|
|
HTTP: "127.0.0.1",
|
|
RPC: "127.0.0.1",
|
|
Serf: "127.0.0.1",
|
|
},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
RPC: "127.0.0.1",
|
|
Serf: "127.0.0.1",
|
|
},
|
|
HTTPAPIResponseHeaders: map[string]string{
|
|
"Access-Control-Allow-Origin": "*",
|
|
},
|
|
Vault: &config.VaultConfig{
|
|
Token: "1",
|
|
AllowUnauthenticated: &falseValue,
|
|
TaskTokenTTL: "1",
|
|
Addr: "1",
|
|
TLSCaFile: "1",
|
|
TLSCaPath: "1",
|
|
TLSCertFile: "1",
|
|
TLSKeyFile: "1",
|
|
TLSSkipVerify: &falseValue,
|
|
TLSServerName: "1",
|
|
},
|
|
Consul: &config.ConsulConfig{
|
|
ServerServiceName: "1",
|
|
ClientServiceName: "1",
|
|
AutoAdvertise: &falseValue,
|
|
Addr: "1",
|
|
Timeout: 1 * time.Second,
|
|
Token: "1",
|
|
Auth: "1",
|
|
EnableSSL: &falseValue,
|
|
VerifySSL: &falseValue,
|
|
CAFile: "1",
|
|
CertFile: "1",
|
|
KeyFile: "1",
|
|
ServerAutoJoin: &falseValue,
|
|
ClientAutoJoin: &falseValue,
|
|
ChecksUseAdvertise: &falseValue,
|
|
},
|
|
Autopilot: &config.AutopilotConfig{
|
|
CleanupDeadServers: &falseValue,
|
|
ServerStabilizationTime: 1 * time.Second,
|
|
LastContactThreshold: 1 * time.Second,
|
|
MaxTrailingLogs: 1,
|
|
EnableRedundancyZones: &falseValue,
|
|
DisableUpgradeMigration: &falseValue,
|
|
EnableCustomUpgrades: &falseValue,
|
|
},
|
|
Plugins: []*config.PluginConfig{
|
|
{
|
|
Name: "docker",
|
|
Args: []string{"foo"},
|
|
Config: map[string]interface{}{
|
|
"bar": 1,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
c3 := &Config{
|
|
Region: "global",
|
|
Datacenter: "dc2",
|
|
NodeName: "node2",
|
|
DataDir: "/tmp/dir2",
|
|
PluginDir: "/tmp/pluginDir2",
|
|
LogLevel: "DEBUG",
|
|
LogJson: true,
|
|
EnableDebug: true,
|
|
LeaveOnInt: true,
|
|
LeaveOnTerm: true,
|
|
EnableSyslog: true,
|
|
SyslogFacility: "local0.debug",
|
|
DisableUpdateCheck: helper.BoolToPtr(true),
|
|
DisableAnonymousSignature: true,
|
|
BindAddr: "127.0.0.2",
|
|
Telemetry: &Telemetry{
|
|
StatsiteAddr: "127.0.0.2:8125",
|
|
StatsdAddr: "127.0.0.2:8125",
|
|
DataDogAddr: "127.0.0.1:8125",
|
|
DataDogTags: []string{"cat1:tag1", "cat2:tag2"},
|
|
PrometheusMetrics: true,
|
|
DisableHostname: true,
|
|
PublishNodeMetrics: true,
|
|
PublishAllocationMetrics: true,
|
|
DisableTaggedMetrics: true,
|
|
BackwardsCompatibleMetrics: true,
|
|
CirconusAPIToken: "1",
|
|
CirconusAPIApp: "nomad",
|
|
CirconusAPIURL: "https://api.circonus.com/v2",
|
|
CirconusSubmissionInterval: "10s",
|
|
CirconusCheckSubmissionURL: "https://example.com/metrics",
|
|
CirconusCheckID: "1",
|
|
CirconusCheckForceMetricActivation: "false",
|
|
CirconusCheckInstanceID: "node2:nomad",
|
|
CirconusCheckSearchTag: "service:nomad",
|
|
CirconusCheckDisplayName: "node2:nomad",
|
|
CirconusCheckTags: "cat1:tag1,cat2:tag2",
|
|
CirconusBrokerID: "1",
|
|
CirconusBrokerSelectTag: "dc:dc2",
|
|
PrefixFilter: []string{"prefix1", "prefix2"},
|
|
DisableDispatchedJobSummaryMetrics: true,
|
|
FilterDefault: helper.BoolToPtr(false),
|
|
},
|
|
Client: &ClientConfig{
|
|
Enabled: true,
|
|
StateDir: "/tmp/state2",
|
|
AllocDir: "/tmp/alloc2",
|
|
NodeClass: "class2",
|
|
Servers: []string{"server2"},
|
|
Meta: map[string]string{
|
|
"baz": "zip",
|
|
},
|
|
Options: map[string]string{
|
|
"foo": "bar",
|
|
"baz": "zip",
|
|
},
|
|
ChrootEnv: map[string]string{},
|
|
ClientMaxPort: 20000,
|
|
ClientMinPort: 22000,
|
|
NetworkSpeed: 105,
|
|
CpuCompute: 105,
|
|
MemoryMB: 105,
|
|
MaxKillTimeout: "50s",
|
|
DisableRemoteExec: false,
|
|
TemplateConfig: &ClientTemplateConfig{
|
|
FunctionBlacklist: []string{"plugin"},
|
|
DisableSandbox: false,
|
|
},
|
|
Reserved: &Resources{
|
|
CPU: 15,
|
|
MemoryMB: 15,
|
|
DiskMB: 15,
|
|
ReservedPorts: "2,10-30,55",
|
|
},
|
|
GCInterval: 6 * time.Second,
|
|
GCParallelDestroys: 6,
|
|
GCDiskUsageThreshold: 71,
|
|
GCInodeUsageThreshold: 86,
|
|
},
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
AuthoritativeRegion: "global2",
|
|
BootstrapExpect: 2,
|
|
DataDir: "/tmp/data2",
|
|
ProtocolVersion: 2,
|
|
RaftProtocol: 2,
|
|
NumSchedulers: helper.IntToPtr(2),
|
|
EnabledSchedulers: []string{structs.JobTypeBatch},
|
|
NodeGCThreshold: "12h",
|
|
HeartbeatGrace: 2 * time.Minute,
|
|
MinHeartbeatTTL: 2 * time.Minute,
|
|
MaxHeartbeatsPerSecond: 200.0,
|
|
RejoinAfterLeave: true,
|
|
StartJoin: []string{"1.1.1.1"},
|
|
RetryJoin: []string{"1.1.1.1"},
|
|
RetryInterval: time.Second * 10,
|
|
NonVotingServer: true,
|
|
RedundancyZone: "bar",
|
|
UpgradeVersion: "bar",
|
|
},
|
|
ACL: &ACLConfig{
|
|
Enabled: true,
|
|
TokenTTL: 20 * time.Second,
|
|
PolicyTTL: 20 * time.Second,
|
|
ReplicationToken: "foobar",
|
|
},
|
|
Ports: &Ports{
|
|
HTTP: 20000,
|
|
RPC: 21000,
|
|
Serf: 22000,
|
|
},
|
|
Addresses: &Addresses{
|
|
HTTP: "127.0.0.2",
|
|
RPC: "127.0.0.2",
|
|
Serf: "127.0.0.2",
|
|
},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
RPC: "127.0.0.2",
|
|
Serf: "127.0.0.2",
|
|
},
|
|
HTTPAPIResponseHeaders: map[string]string{
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
},
|
|
Vault: &config.VaultConfig{
|
|
Token: "2",
|
|
AllowUnauthenticated: &trueValue,
|
|
TaskTokenTTL: "2",
|
|
Addr: "2",
|
|
TLSCaFile: "2",
|
|
TLSCaPath: "2",
|
|
TLSCertFile: "2",
|
|
TLSKeyFile: "2",
|
|
TLSSkipVerify: &trueValue,
|
|
TLSServerName: "2",
|
|
},
|
|
Consul: &config.ConsulConfig{
|
|
ServerServiceName: "2",
|
|
ClientServiceName: "2",
|
|
AutoAdvertise: &trueValue,
|
|
Addr: "2",
|
|
Timeout: 2 * time.Second,
|
|
Token: "2",
|
|
Auth: "2",
|
|
EnableSSL: &trueValue,
|
|
VerifySSL: &trueValue,
|
|
CAFile: "2",
|
|
CertFile: "2",
|
|
KeyFile: "2",
|
|
ServerAutoJoin: &trueValue,
|
|
ClientAutoJoin: &trueValue,
|
|
ChecksUseAdvertise: &trueValue,
|
|
},
|
|
Sentinel: &config.SentinelConfig{
|
|
Imports: []*config.SentinelImport{
|
|
{
|
|
Name: "foo",
|
|
Path: "foo",
|
|
Args: []string{"a", "b", "c"},
|
|
},
|
|
},
|
|
},
|
|
Autopilot: &config.AutopilotConfig{
|
|
CleanupDeadServers: &trueValue,
|
|
ServerStabilizationTime: 2 * time.Second,
|
|
LastContactThreshold: 2 * time.Second,
|
|
MaxTrailingLogs: 2,
|
|
EnableRedundancyZones: &trueValue,
|
|
DisableUpgradeMigration: &trueValue,
|
|
EnableCustomUpgrades: &trueValue,
|
|
},
|
|
Plugins: []*config.PluginConfig{
|
|
{
|
|
Name: "docker",
|
|
Args: []string{"bam"},
|
|
Config: map[string]interface{}{
|
|
"baz": 2,
|
|
},
|
|
},
|
|
{
|
|
Name: "exec",
|
|
Args: []string{"arg"},
|
|
Config: map[string]interface{}{
|
|
"config": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
result := c0.Merge(c1)
|
|
result = result.Merge(c2)
|
|
result = result.Merge(c3)
|
|
if !reflect.DeepEqual(result, c3) {
|
|
t.Fatalf("bad:\n%#v\n%#v", result, c3)
|
|
}
|
|
}
|
|
|
|
func TestConfig_ParseConfigFile(t *testing.T) {
|
|
// Fails if the file doesn't exist
|
|
if _, err := ParseConfigFile("/unicorns/leprechauns"); err == nil {
|
|
t.Fatalf("expected error, got nothing")
|
|
}
|
|
|
|
fh, err := ioutil.TempFile("", "nomad")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer os.RemoveAll(fh.Name())
|
|
|
|
// Invalid content returns error
|
|
if _, err := fh.WriteString("nope;!!!"); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if _, err := ParseConfigFile(fh.Name()); err == nil {
|
|
t.Fatalf("expected load error, got nothing")
|
|
}
|
|
|
|
// Valid content parses successfully
|
|
if err := fh.Truncate(0); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if _, err := fh.Seek(0, 0); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if _, err := fh.WriteString(`{"region":"west"}`); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
config, err := ParseConfigFile(fh.Name())
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config.Region != "west" {
|
|
t.Fatalf("bad region: %q", config.Region)
|
|
}
|
|
}
|
|
|
|
func TestConfig_LoadConfigDir(t *testing.T) {
|
|
// Fails if the dir doesn't exist.
|
|
if _, err := LoadConfigDir("/unicorns/leprechauns"); err == nil {
|
|
t.Fatalf("expected error, got nothing")
|
|
}
|
|
|
|
dir, err := ioutil.TempDir("", "nomad")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
// Returns empty config on empty dir
|
|
config, err := LoadConfig(dir)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config == nil {
|
|
t.Fatalf("should not be nil")
|
|
}
|
|
|
|
file1 := filepath.Join(dir, "conf1.hcl")
|
|
err = ioutil.WriteFile(file1, []byte(`{"region":"west"}`), 0600)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
file2 := filepath.Join(dir, "conf2.hcl")
|
|
err = ioutil.WriteFile(file2, []byte(`{"datacenter":"sfo"}`), 0600)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
file3 := filepath.Join(dir, "conf3.hcl")
|
|
err = ioutil.WriteFile(file3, []byte(`nope;!!!`), 0600)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Fails if we have a bad config file
|
|
if _, err := LoadConfigDir(dir); err == nil {
|
|
t.Fatalf("expected load error, got nothing")
|
|
}
|
|
|
|
if err := os.Remove(file3); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Works if configs are valid
|
|
config, err = LoadConfigDir(dir)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config.Region != "west" || config.Datacenter != "sfo" {
|
|
t.Fatalf("bad: %#v", config)
|
|
}
|
|
}
|
|
|
|
func TestConfig_LoadConfig(t *testing.T) {
|
|
// Fails if the target doesn't exist
|
|
if _, err := LoadConfig("/unicorns/leprechauns"); err == nil {
|
|
t.Fatalf("expected error, got nothing")
|
|
}
|
|
|
|
fh, err := ioutil.TempFile("", "nomad")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer os.Remove(fh.Name())
|
|
|
|
if _, err := fh.WriteString(`{"region":"west"}`); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Works on a config file
|
|
config, err := LoadConfig(fh.Name())
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config.Region != "west" {
|
|
t.Fatalf("bad: %#v", config)
|
|
}
|
|
|
|
expectedConfigFiles := []string{fh.Name()}
|
|
if !reflect.DeepEqual(config.Files, expectedConfigFiles) {
|
|
t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n",
|
|
expectedConfigFiles, config.Files)
|
|
}
|
|
|
|
dir, err := ioutil.TempDir("", "nomad")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
file1 := filepath.Join(dir, "config1.hcl")
|
|
err = ioutil.WriteFile(file1, []byte(`{"datacenter":"sfo"}`), 0600)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Works on config dir
|
|
config, err = LoadConfig(dir)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config.Datacenter != "sfo" {
|
|
t.Fatalf("bad: %#v", config)
|
|
}
|
|
|
|
expectedConfigFiles = []string{file1}
|
|
if !reflect.DeepEqual(config.Files, expectedConfigFiles) {
|
|
t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n",
|
|
expectedConfigFiles, config.Files)
|
|
}
|
|
}
|
|
|
|
func TestConfig_LoadConfigsFileOrder(t *testing.T) {
|
|
config1, err := LoadConfigDir("test-resources/etcnomad")
|
|
if err != nil {
|
|
t.Fatalf("Failed to load config: %s", err)
|
|
}
|
|
|
|
config2, err := LoadConfig("test-resources/myconf")
|
|
if err != nil {
|
|
t.Fatalf("Failed to load config: %s", err)
|
|
}
|
|
|
|
expected := []string{
|
|
// filepath.FromSlash changes these to backslash \ on Windows
|
|
filepath.FromSlash("test-resources/etcnomad/common.hcl"),
|
|
filepath.FromSlash("test-resources/etcnomad/server.json"),
|
|
filepath.FromSlash("test-resources/myconf"),
|
|
}
|
|
|
|
config := config1.Merge(config2)
|
|
|
|
if !reflect.DeepEqual(config.Files, expected) {
|
|
t.Errorf("Loaded configs don't match\nwant: %+v\n got: %+v\n",
|
|
expected, config.Files)
|
|
}
|
|
}
|
|
|
|
func TestConfig_Listener(t *testing.T) {
|
|
config := DefaultConfig()
|
|
|
|
// Fails on invalid input
|
|
if ln, err := config.Listener("tcp", "nope", 8080); err == nil {
|
|
ln.Close()
|
|
t.Fatalf("expected addr error")
|
|
}
|
|
if ln, err := config.Listener("nope", "127.0.0.1", 8080); err == nil {
|
|
ln.Close()
|
|
t.Fatalf("expected protocol err")
|
|
}
|
|
if ln, err := config.Listener("tcp", "127.0.0.1", -1); err == nil {
|
|
ln.Close()
|
|
t.Fatalf("expected port error")
|
|
}
|
|
|
|
// Works with valid inputs
|
|
ports := freeport.MustTake(2)
|
|
defer freeport.Return(ports)
|
|
|
|
ln, err := config.Listener("tcp", "127.0.0.1", ports[0])
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
ln.Close()
|
|
|
|
if net := ln.Addr().Network(); net != "tcp" {
|
|
t.Fatalf("expected tcp, got: %q", net)
|
|
}
|
|
want := fmt.Sprintf("127.0.0.1:%d", ports[0])
|
|
if addr := ln.Addr().String(); addr != want {
|
|
t.Fatalf("expected %q, got: %q", want, addr)
|
|
}
|
|
|
|
// Falls back to default bind address if non provided
|
|
config.BindAddr = "0.0.0.0"
|
|
ln, err = config.Listener("tcp4", "", ports[1])
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
ln.Close()
|
|
|
|
want = fmt.Sprintf("0.0.0.0:%d", ports[1])
|
|
if addr := ln.Addr().String(); addr != want {
|
|
t.Fatalf("expected %q, got: %q", want, addr)
|
|
}
|
|
}
|
|
|
|
func TestConfig_DevModeFlag(t *testing.T) {
|
|
cases := []struct {
|
|
dev bool
|
|
connect bool
|
|
expected *devModeConfig
|
|
expectedErr string
|
|
}{}
|
|
if runtime.GOOS != "linux" {
|
|
cases = []struct {
|
|
dev bool
|
|
connect bool
|
|
expected *devModeConfig
|
|
expectedErr string
|
|
}{
|
|
{false, false, nil, ""},
|
|
{true, false, &devModeConfig{defaultMode: true, connectMode: false}, ""},
|
|
{true, true, nil, "-dev-connect is only supported on linux"},
|
|
{false, true, nil, "-dev-connect is only supported on linux"},
|
|
}
|
|
}
|
|
if runtime.GOOS == "linux" {
|
|
testutil.RequireRoot(t)
|
|
cases = []struct {
|
|
dev bool
|
|
connect bool
|
|
expected *devModeConfig
|
|
expectedErr string
|
|
}{
|
|
{false, false, nil, ""},
|
|
{true, false, &devModeConfig{defaultMode: true, connectMode: false}, ""},
|
|
{true, true, &devModeConfig{defaultMode: true, connectMode: true}, ""},
|
|
{false, true, &devModeConfig{defaultMode: false, connectMode: true}, ""},
|
|
}
|
|
}
|
|
for _, c := range cases {
|
|
t.Run("", func(t *testing.T) {
|
|
mode, err := newDevModeConfig(c.dev, c.connect)
|
|
if err != nil && c.expectedErr == "" {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err != nil && !strings.Contains(err.Error(), c.expectedErr) {
|
|
t.Fatalf("expected %s; got %v", c.expectedErr, err)
|
|
}
|
|
if mode == nil && c.expected != nil {
|
|
t.Fatalf("expected %+v but got nil", c.expected)
|
|
}
|
|
if mode != nil {
|
|
if c.expected.defaultMode != mode.defaultMode ||
|
|
c.expected.connectMode != mode.connectMode {
|
|
t.Fatalf("expected %+v, got %+v", c.expected, mode)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConfig_normalizeAddrs_DevMode asserts that normalizeAddrs allows
|
|
// advertising localhost in dev mode.
|
|
func TestConfig_normalizeAddrs_DevMode(t *testing.T) {
|
|
// allow to advertise 127.0.0.1 if dev-mode is enabled
|
|
c := &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{},
|
|
DevMode: true,
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unable to normalize addresses: %s", err)
|
|
}
|
|
|
|
if c.BindAddr != "127.0.0.1" {
|
|
t.Fatalf("expected BindAddr 127.0.0.1, got %s", c.BindAddr)
|
|
}
|
|
|
|
if c.normalizedAddrs.HTTP != "127.0.0.1:4646" {
|
|
t.Fatalf("expected HTTP address 127.0.0.1:4646, got %s", c.normalizedAddrs.HTTP)
|
|
}
|
|
|
|
if c.normalizedAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Fatalf("expected RPC address 127.0.0.1:4647, got %s", c.normalizedAddrs.RPC)
|
|
}
|
|
|
|
if c.normalizedAddrs.Serf != "127.0.0.1:4648" {
|
|
t.Fatalf("expected Serf address 127.0.0.1:4648, got %s", c.normalizedAddrs.Serf)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
|
|
t.Fatalf("expected HTTP advertise address 127.0.0.1:4646, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
// Client mode, no Serf address defined
|
|
if c.AdvertiseAddrs.Serf != "" {
|
|
t.Fatalf("expected unset Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
|
|
}
|
|
}
|
|
|
|
// TestConfig_normalizeAddrs_NoAdvertise asserts that normalizeAddrs will
|
|
// fail if no valid advertise address available in non-dev mode.
|
|
func TestConfig_normalizeAddrs_NoAdvertise(t *testing.T) {
|
|
c := &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{},
|
|
DevMode: false,
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err == nil {
|
|
t.Fatalf("expected an error when no valid advertise address is available")
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP == "127.0.0.1:4646" {
|
|
t.Fatalf("expected non-localhost HTTP advertise address, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC == "127.0.0.1:4647" {
|
|
t.Fatalf("expected non-localhost RPC advertise address, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.Serf == "127.0.0.1:4648" {
|
|
t.Fatalf("expected non-localhost Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
|
|
}
|
|
}
|
|
|
|
// TestConfig_normalizeAddrs_AdvertiseLocalhost asserts localhost can be
|
|
// advertised if it's explicitly set in the config.
|
|
func TestConfig_normalizeAddrs_AdvertiseLocalhost(t *testing.T) {
|
|
c := &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "127.0.0.1",
|
|
RPC: "127.0.0.1",
|
|
Serf: "127.0.0.1",
|
|
},
|
|
DevMode: false,
|
|
Server: &ServerConfig{Enabled: true},
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unexpected error when manually setting bind mode: %v", err)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
|
|
t.Errorf("expected localhost HTTP advertise address, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Errorf("expected localhost RPC advertise address, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.Serf != "127.0.0.1:4648" {
|
|
t.Errorf("expected localhost Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
|
|
}
|
|
}
|
|
|
|
// TestConfig_normalizeAddrs_IPv6Loopback asserts that an IPv6 loopback address
|
|
// is normalized properly. See #2739
|
|
func TestConfig_normalizeAddrs_IPv6Loopback(t *testing.T) {
|
|
c := &Config{
|
|
BindAddr: "::1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "::1",
|
|
RPC: "::1",
|
|
},
|
|
DevMode: false,
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unexpected error when manually setting bind mode: %v", err)
|
|
}
|
|
|
|
if c.Addresses.HTTP != "::1" {
|
|
t.Errorf("expected ::1 HTTP address, got %s", c.Addresses.HTTP)
|
|
}
|
|
|
|
if c.Addresses.RPC != "::1" {
|
|
t.Errorf("expected ::1 RPC address, got %s", c.Addresses.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "[::1]:4646" {
|
|
t.Errorf("expected [::1] HTTP advertise address, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "[::1]:4647" {
|
|
t.Errorf("expected [::1] RPC advertise address, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
}
|
|
|
|
func TestConfig_normalizeAddrs(t *testing.T) {
|
|
c := &Config{
|
|
BindAddr: "169.254.1.5",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{
|
|
HTTP: "169.254.1.10",
|
|
},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
RPC: "169.254.1.40",
|
|
},
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
},
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unable to normalize addresses: %s", err)
|
|
}
|
|
|
|
if c.BindAddr != "169.254.1.5" {
|
|
t.Fatalf("expected BindAddr 169.254.1.5, got %s", c.BindAddr)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "169.254.1.10:4646" {
|
|
t.Fatalf("expected HTTP advertise address 169.254.1.10:4646, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "169.254.1.40:4647" {
|
|
t.Fatalf("expected RPC advertise address 169.254.1.40:4647, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.Serf != "169.254.1.5:4648" {
|
|
t.Fatalf("expected Serf advertise address 169.254.1.5:4648, got %s", c.AdvertiseAddrs.Serf)
|
|
}
|
|
|
|
c = &Config{
|
|
BindAddr: "{{ GetPrivateIP }}",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
RPC: "{{ GetPrivateIP }}",
|
|
},
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
},
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unable to normalize addresses: %s", err)
|
|
}
|
|
|
|
exp := net.JoinHostPort(c.BindAddr, "4646")
|
|
if c.AdvertiseAddrs.HTTP != exp {
|
|
t.Fatalf("expected HTTP advertise address %s, got %s", exp, c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
exp = net.JoinHostPort(c.BindAddr, "4647")
|
|
if c.AdvertiseAddrs.RPC != exp {
|
|
t.Fatalf("expected RPC advertise address %s, got %s", exp, c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
exp = net.JoinHostPort(c.BindAddr, "4648")
|
|
if c.AdvertiseAddrs.Serf != exp {
|
|
t.Fatalf("expected Serf advertise address %s, got %s", exp, c.AdvertiseAddrs.Serf)
|
|
}
|
|
|
|
// allow to advertise 127.0.0.1 in non-dev mode, if explicitly configured to do so
|
|
c = &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "127.0.0.1:4646",
|
|
RPC: "127.0.0.1:4647",
|
|
Serf: "127.0.0.1:4648",
|
|
},
|
|
DevMode: false,
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
},
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unable to normalize addresses: %s", err)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
|
|
t.Fatalf("expected HTTP advertise address 127.0.0.1:4646, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
}
|
|
|
|
func TestIsMissingPort(t *testing.T) {
|
|
_, _, err := net.SplitHostPort("localhost")
|
|
if missing := isMissingPort(err); !missing {
|
|
t.Errorf("expected missing port error, but got %v", err)
|
|
}
|
|
_, _, err = net.SplitHostPort("localhost:9000")
|
|
if missing := isMissingPort(err); missing {
|
|
t.Errorf("expected no error, but got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMergeServerJoin(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
a := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
b := &ServerJoin{}
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
a := &ServerJoin{}
|
|
b := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
var a *ServerJoin
|
|
b := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
a := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
var b *ServerJoin
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
a := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
}
|
|
b := &ServerJoin{
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
}
|
|
|
|
func TestTelemetry_PrefixFilters(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
in []string
|
|
expAllow []string
|
|
expBlock []string
|
|
expErr bool
|
|
}{
|
|
{
|
|
in: []string{"+foo"},
|
|
expAllow: []string{"foo"},
|
|
},
|
|
{
|
|
in: []string{"-foo"},
|
|
expBlock: []string{"foo"},
|
|
},
|
|
{
|
|
in: []string{"+a.b.c", "-x.y.z"},
|
|
expAllow: []string{"a.b.c"},
|
|
expBlock: []string{"x.y.z"},
|
|
},
|
|
{
|
|
in: []string{"+foo", "bad", "-bar"},
|
|
expErr: true,
|
|
},
|
|
}
|
|
|
|
for i, c := range cases {
|
|
t.Run(fmt.Sprintf("PrefixCase%d", i), func(t *testing.T) {
|
|
require := require.New(t)
|
|
tel := &Telemetry{
|
|
PrefixFilter: c.in,
|
|
}
|
|
|
|
allow, block, err := tel.PrefixFilters()
|
|
require.Exactly(c.expAllow, allow)
|
|
require.Exactly(c.expBlock, block)
|
|
require.Equal(c.expErr, err != nil)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTelemetry_Parse(t *testing.T) {
|
|
require := require.New(t)
|
|
dir, err := ioutil.TempDir("", "nomad")
|
|
require.NoError(err)
|
|
defer os.RemoveAll(dir)
|
|
|
|
file1 := filepath.Join(dir, "config1.hcl")
|
|
err = ioutil.WriteFile(file1, []byte(`telemetry{
|
|
prefix_filter = ["+nomad.raft"]
|
|
filter_default = false
|
|
disable_dispatched_job_summary_metrics = true
|
|
}`), 0600)
|
|
require.NoError(err)
|
|
|
|
// Works on config dir
|
|
config, err := LoadConfig(dir)
|
|
require.NoError(err)
|
|
|
|
require.False(*config.Telemetry.FilterDefault)
|
|
require.Exactly([]string{"+nomad.raft"}, config.Telemetry.PrefixFilter)
|
|
require.True(config.Telemetry.DisableDispatchedJobSummaryMetrics)
|
|
}
|