907 lines
24 KiB
Go
907 lines
24 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package agent
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/helper/pointer"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/nomad/structs/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var basicConfig = &Config{
|
|
Region: "foobar",
|
|
Datacenter: "dc2",
|
|
NodeName: "my-web",
|
|
DataDir: "/tmp/nomad",
|
|
PluginDir: "/tmp/nomad-plugins",
|
|
LogFile: "/var/log/nomad.log",
|
|
LogLevel: "ERR",
|
|
LogJson: true,
|
|
BindAddr: "192.168.0.1",
|
|
EnableDebug: true,
|
|
Ports: &Ports{
|
|
HTTP: 1234,
|
|
RPC: 2345,
|
|
Serf: 3456,
|
|
},
|
|
Addresses: &Addresses{
|
|
HTTP: "127.0.0.1",
|
|
RPC: "127.0.0.2",
|
|
Serf: "127.0.0.3",
|
|
},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
RPC: "127.0.0.3",
|
|
Serf: "127.0.0.4",
|
|
},
|
|
Client: &ClientConfig{
|
|
Enabled: true,
|
|
StateDir: "/tmp/client-state",
|
|
AllocDir: "/tmp/alloc",
|
|
Servers: []string{"a.b.c:80", "127.0.0.1:1234"},
|
|
NodeClass: "linux-medium-64bit",
|
|
ServerJoin: &ServerJoin{
|
|
RetryJoin: []string{"1.1.1.1", "2.2.2.2"},
|
|
RetryInterval: time.Duration(15) * time.Second,
|
|
RetryIntervalHCL: "15s",
|
|
RetryMaxAttempts: 3,
|
|
},
|
|
Meta: map[string]string{
|
|
"foo": "bar",
|
|
"baz": "zip",
|
|
},
|
|
Options: map[string]string{
|
|
"foo": "bar",
|
|
"baz": "zip",
|
|
},
|
|
ChrootEnv: map[string]string{
|
|
"/opt/myapp/etc": "/etc",
|
|
"/opt/myapp/bin": "/bin",
|
|
},
|
|
NetworkInterface: "eth0",
|
|
NetworkSpeed: 100,
|
|
CpuCompute: 4444,
|
|
MemoryMB: 0,
|
|
MaxKillTimeout: "10s",
|
|
ClientMinPort: 1000,
|
|
ClientMaxPort: 2000,
|
|
Reserved: &Resources{
|
|
CPU: 10,
|
|
MemoryMB: 10,
|
|
DiskMB: 10,
|
|
ReservedPorts: "1,100,10-12",
|
|
},
|
|
GCInterval: 6 * time.Second,
|
|
GCIntervalHCL: "6s",
|
|
GCParallelDestroys: 6,
|
|
GCDiskUsageThreshold: 82,
|
|
GCInodeUsageThreshold: 91,
|
|
GCMaxAllocs: 50,
|
|
NoHostUUID: pointer.Of(false),
|
|
DisableRemoteExec: true,
|
|
HostVolumes: []*structs.ClientHostVolumeConfig{
|
|
{Name: "tmp", Path: "/tmp"},
|
|
},
|
|
CNIPath: "/tmp/cni_path",
|
|
BridgeNetworkName: "custom_bridge_name",
|
|
BridgeNetworkSubnet: "custom_bridge_subnet",
|
|
},
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
AuthoritativeRegion: "foobar",
|
|
BootstrapExpect: 5,
|
|
DataDir: "/tmp/data",
|
|
RaftProtocol: 3,
|
|
RaftMultiplier: pointer.Of(4),
|
|
NumSchedulers: pointer.Of(2),
|
|
EnabledSchedulers: []string{"test"},
|
|
NodeGCThreshold: "12h",
|
|
EvalGCThreshold: "12h",
|
|
JobGCInterval: "3m",
|
|
JobGCThreshold: "12h",
|
|
DeploymentGCThreshold: "12h",
|
|
CSIVolumeClaimGCInterval: "3m",
|
|
CSIVolumeClaimGCThreshold: "12h",
|
|
CSIPluginGCThreshold: "12h",
|
|
ACLTokenGCThreshold: "12h",
|
|
HeartbeatGrace: 30 * time.Second,
|
|
HeartbeatGraceHCL: "30s",
|
|
MinHeartbeatTTL: 33 * time.Second,
|
|
MinHeartbeatTTLHCL: "33s",
|
|
MaxHeartbeatsPerSecond: 11.0,
|
|
FailoverHeartbeatTTL: 330 * time.Second,
|
|
FailoverHeartbeatTTLHCL: "330s",
|
|
RetryJoin: []string{"1.1.1.1", "2.2.2.2"},
|
|
StartJoin: []string{"1.1.1.1", "2.2.2.2"},
|
|
RetryInterval: 15 * time.Second,
|
|
RetryIntervalHCL: "15s",
|
|
RejoinAfterLeave: true,
|
|
RetryMaxAttempts: 3,
|
|
NonVotingServer: true,
|
|
RedundancyZone: "foo",
|
|
UpgradeVersion: "0.8.0",
|
|
EncryptKey: "abc",
|
|
EnableEventBroker: pointer.Of(false),
|
|
EventBufferSize: pointer.Of(200),
|
|
PlanRejectionTracker: &PlanRejectionTracker{
|
|
Enabled: pointer.Of(true),
|
|
NodeThreshold: 100,
|
|
NodeWindow: 41 * time.Minute,
|
|
NodeWindowHCL: "41m",
|
|
},
|
|
ServerJoin: &ServerJoin{
|
|
RetryJoin: []string{"1.1.1.1", "2.2.2.2"},
|
|
RetryInterval: time.Duration(15) * time.Second,
|
|
RetryIntervalHCL: "15s",
|
|
RetryMaxAttempts: 3,
|
|
},
|
|
DefaultSchedulerConfig: &structs.SchedulerConfiguration{
|
|
SchedulerAlgorithm: "spread",
|
|
PreemptionConfig: structs.PreemptionConfig{
|
|
SystemSchedulerEnabled: true,
|
|
BatchSchedulerEnabled: true,
|
|
ServiceSchedulerEnabled: true,
|
|
},
|
|
},
|
|
LicensePath: "/tmp/nomad.hclic",
|
|
JobDefaultPriority: pointer.Of(100),
|
|
JobMaxPriority: pointer.Of(200),
|
|
},
|
|
ACL: &ACLConfig{
|
|
Enabled: true,
|
|
TokenTTL: 60 * time.Second,
|
|
TokenTTLHCL: "60s",
|
|
PolicyTTL: 60 * time.Second,
|
|
PolicyTTLHCL: "60s",
|
|
RoleTTLHCL: "60s",
|
|
RoleTTL: 60 * time.Second,
|
|
TokenMinExpirationTTLHCL: "1h",
|
|
TokenMinExpirationTTL: 1 * time.Hour,
|
|
TokenMaxExpirationTTLHCL: "100h",
|
|
TokenMaxExpirationTTL: 100 * time.Hour,
|
|
ReplicationToken: "foobar",
|
|
},
|
|
Audit: &config.AuditConfig{
|
|
Enabled: pointer.Of(true),
|
|
Sinks: []*config.AuditSink{
|
|
{
|
|
DeliveryGuarantee: "enforced",
|
|
Name: "file",
|
|
Type: "file",
|
|
Format: "json",
|
|
Path: "/opt/nomad/audit.log",
|
|
RotateDuration: 24 * time.Hour,
|
|
RotateDurationHCL: "24h",
|
|
RotateBytes: 100,
|
|
RotateMaxFiles: 10,
|
|
},
|
|
},
|
|
Filters: []*config.AuditFilter{
|
|
{
|
|
Name: "default",
|
|
Type: "HTTPEvent",
|
|
Endpoints: []string{"/v1/metrics"},
|
|
Stages: []string{"*"},
|
|
Operations: []string{"*"},
|
|
},
|
|
},
|
|
},
|
|
Telemetry: &Telemetry{
|
|
StatsiteAddr: "127.0.0.1:1234",
|
|
StatsdAddr: "127.0.0.1:2345",
|
|
PrometheusMetrics: true,
|
|
DisableHostname: true,
|
|
UseNodeName: false,
|
|
CollectionInterval: "3s",
|
|
collectionInterval: 3 * time.Second,
|
|
PublishAllocationMetrics: true,
|
|
PublishNodeMetrics: true,
|
|
},
|
|
LeaveOnInt: true,
|
|
LeaveOnTerm: true,
|
|
EnableSyslog: true,
|
|
SyslogFacility: "LOCAL1",
|
|
DisableUpdateCheck: pointer.Of(true),
|
|
DisableAnonymousSignature: true,
|
|
Consul: &config.ConsulConfig{
|
|
ServerServiceName: "nomad",
|
|
ServerHTTPCheckName: "nomad-server-http-health-check",
|
|
ServerSerfCheckName: "nomad-server-serf-health-check",
|
|
ServerRPCCheckName: "nomad-server-rpc-health-check",
|
|
ClientServiceName: "nomad-client",
|
|
ClientHTTPCheckName: "nomad-client-http-health-check",
|
|
Addr: "127.0.0.1:9500",
|
|
AllowUnauthenticated: &trueValue,
|
|
Token: "token1",
|
|
Auth: "username:pass",
|
|
EnableSSL: &trueValue,
|
|
VerifySSL: &trueValue,
|
|
CAFile: "/path/to/ca/file",
|
|
CertFile: "/path/to/cert/file",
|
|
KeyFile: "/path/to/key/file",
|
|
ServerAutoJoin: &trueValue,
|
|
ClientAutoJoin: &trueValue,
|
|
AutoAdvertise: &trueValue,
|
|
ChecksUseAdvertise: &trueValue,
|
|
Timeout: 5 * time.Second,
|
|
TimeoutHCL: "5s",
|
|
},
|
|
Vault: &config.VaultConfig{
|
|
Addr: "127.0.0.1:9500",
|
|
AllowUnauthenticated: &trueValue,
|
|
ConnectionRetryIntv: config.DefaultVaultConnectRetryIntv,
|
|
Enabled: &falseValue,
|
|
Role: "test_role",
|
|
TLSCaFile: "/path/to/ca/file",
|
|
TLSCaPath: "/path/to/ca",
|
|
TLSCertFile: "/path/to/cert/file",
|
|
TLSKeyFile: "/path/to/key/file",
|
|
TLSServerName: "foobar",
|
|
TLSSkipVerify: &trueValue,
|
|
TaskTokenTTL: "1s",
|
|
Token: "12345",
|
|
},
|
|
TLSConfig: &config.TLSConfig{
|
|
EnableHTTP: true,
|
|
EnableRPC: true,
|
|
VerifyServerHostname: true,
|
|
CAFile: "foo",
|
|
CertFile: "bar",
|
|
KeyFile: "pipe",
|
|
RPCUpgradeMode: true,
|
|
VerifyHTTPSClient: true,
|
|
TLSPreferServerCipherSuites: true,
|
|
TLSCipherSuites: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
TLSMinVersion: "tls12",
|
|
},
|
|
HTTPAPIResponseHeaders: map[string]string{
|
|
"Access-Control-Allow-Origin": "*",
|
|
},
|
|
Sentinel: &config.SentinelConfig{
|
|
Imports: []*config.SentinelImport{
|
|
{
|
|
Name: "foo",
|
|
Path: "foo",
|
|
Args: []string{"a", "b", "c"},
|
|
},
|
|
{
|
|
Name: "bar",
|
|
Path: "bar",
|
|
Args: []string{"x", "y", "z"},
|
|
},
|
|
},
|
|
},
|
|
Autopilot: &config.AutopilotConfig{
|
|
CleanupDeadServers: &trueValue,
|
|
ServerStabilizationTime: 23057 * time.Second,
|
|
ServerStabilizationTimeHCL: "23057s",
|
|
LastContactThreshold: 12705 * time.Second,
|
|
LastContactThresholdHCL: "12705s",
|
|
MaxTrailingLogs: 17849,
|
|
MinQuorum: 3,
|
|
EnableRedundancyZones: &trueValue,
|
|
DisableUpgradeMigration: &trueValue,
|
|
EnableCustomUpgrades: &trueValue,
|
|
},
|
|
Plugins: []*config.PluginConfig{
|
|
{
|
|
Name: "docker",
|
|
Args: []string{"foo", "bar"},
|
|
Config: map[string]interface{}{
|
|
"foo": "bar",
|
|
"nested": []map[string]interface{}{
|
|
{
|
|
"bam": 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "exec",
|
|
Config: map[string]interface{}{
|
|
"foo": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var pluginConfig = &Config{
|
|
Region: "",
|
|
Datacenter: "",
|
|
NodeName: "",
|
|
DataDir: "",
|
|
PluginDir: "",
|
|
LogLevel: "",
|
|
BindAddr: "",
|
|
EnableDebug: false,
|
|
Ports: nil,
|
|
Addresses: nil,
|
|
AdvertiseAddrs: nil,
|
|
Client: &ClientConfig{
|
|
Enabled: false,
|
|
StateDir: "",
|
|
AllocDir: "",
|
|
Servers: nil,
|
|
NodeClass: "",
|
|
Meta: nil,
|
|
Options: nil,
|
|
ChrootEnv: nil,
|
|
NetworkInterface: "",
|
|
NetworkSpeed: 0,
|
|
CpuCompute: 0,
|
|
MemoryMB: 5555,
|
|
MaxKillTimeout: "",
|
|
ClientMinPort: 0,
|
|
ClientMaxPort: 0,
|
|
Reserved: nil,
|
|
GCInterval: 0,
|
|
GCParallelDestroys: 0,
|
|
GCDiskUsageThreshold: 0,
|
|
GCInodeUsageThreshold: 0,
|
|
GCMaxAllocs: 0,
|
|
NoHostUUID: nil,
|
|
},
|
|
Server: nil,
|
|
ACL: nil,
|
|
Telemetry: nil,
|
|
LeaveOnInt: false,
|
|
LeaveOnTerm: false,
|
|
EnableSyslog: false,
|
|
SyslogFacility: "",
|
|
DisableUpdateCheck: nil,
|
|
DisableAnonymousSignature: false,
|
|
Consul: nil,
|
|
Vault: nil,
|
|
TLSConfig: nil,
|
|
HTTPAPIResponseHeaders: map[string]string{},
|
|
Sentinel: nil,
|
|
Plugins: []*config.PluginConfig{
|
|
{
|
|
Name: "docker",
|
|
Config: map[string]interface{}{
|
|
"allow_privileged": true,
|
|
},
|
|
},
|
|
{
|
|
Name: "raw_exec",
|
|
Config: map[string]interface{}{
|
|
"enabled": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var nonoptConfig = &Config{
|
|
Region: "",
|
|
Datacenter: "",
|
|
NodeName: "",
|
|
DataDir: "",
|
|
PluginDir: "",
|
|
LogLevel: "",
|
|
BindAddr: "",
|
|
EnableDebug: false,
|
|
Ports: nil,
|
|
Addresses: nil,
|
|
AdvertiseAddrs: nil,
|
|
Client: &ClientConfig{
|
|
Enabled: false,
|
|
StateDir: "",
|
|
AllocDir: "",
|
|
Servers: nil,
|
|
NodeClass: "",
|
|
Meta: nil,
|
|
Options: nil,
|
|
ChrootEnv: nil,
|
|
NetworkInterface: "",
|
|
NetworkSpeed: 0,
|
|
CpuCompute: 0,
|
|
MemoryMB: 5555,
|
|
MaxKillTimeout: "",
|
|
ClientMinPort: 0,
|
|
ClientMaxPort: 0,
|
|
Reserved: nil,
|
|
GCInterval: 0,
|
|
GCParallelDestroys: 0,
|
|
GCDiskUsageThreshold: 0,
|
|
GCInodeUsageThreshold: 0,
|
|
GCMaxAllocs: 0,
|
|
NoHostUUID: nil,
|
|
},
|
|
Server: nil,
|
|
ACL: nil,
|
|
Telemetry: nil,
|
|
LeaveOnInt: false,
|
|
LeaveOnTerm: false,
|
|
EnableSyslog: false,
|
|
SyslogFacility: "",
|
|
DisableUpdateCheck: nil,
|
|
DisableAnonymousSignature: false,
|
|
Consul: nil,
|
|
Vault: nil,
|
|
TLSConfig: nil,
|
|
HTTPAPIResponseHeaders: map[string]string{},
|
|
Sentinel: nil,
|
|
}
|
|
|
|
func TestConfig_ParseMerge(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
path, err := filepath.Abs(filepath.Join(".", "testdata", "basic.hcl"))
|
|
require.NoError(t, err)
|
|
|
|
actual, err := ParseConfigFile(path)
|
|
require.NoError(t, err)
|
|
|
|
// The Vault connection retry interval is an internal only configuration
|
|
// option, and therefore needs to be added here to ensure the test passes.
|
|
actual.Vault.ConnectionRetryIntv = config.DefaultVaultConnectRetryIntv
|
|
require.Equal(t, basicConfig, actual)
|
|
|
|
oldDefault := &Config{
|
|
Consul: config.DefaultConsulConfig(),
|
|
Vault: config.DefaultVaultConfig(),
|
|
Autopilot: config.DefaultAutopilotConfig(),
|
|
Client: &ClientConfig{},
|
|
Server: &ServerConfig{},
|
|
Audit: &config.AuditConfig{},
|
|
}
|
|
merged := oldDefault.Merge(actual)
|
|
require.Equal(t, basicConfig, merged)
|
|
}
|
|
|
|
func TestConfig_Parse(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
basicConfig.addDefaults()
|
|
pluginConfig.addDefaults()
|
|
nonoptConfig.addDefaults()
|
|
|
|
cases := []struct {
|
|
File string
|
|
Result *Config
|
|
Err bool
|
|
}{
|
|
{
|
|
"basic.hcl",
|
|
basicConfig,
|
|
false,
|
|
},
|
|
{
|
|
"basic.json",
|
|
basicConfig,
|
|
false,
|
|
},
|
|
{
|
|
"plugin.hcl",
|
|
pluginConfig,
|
|
false,
|
|
},
|
|
{
|
|
"plugin.json",
|
|
pluginConfig,
|
|
false,
|
|
},
|
|
{
|
|
"non-optional.hcl",
|
|
nonoptConfig,
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.File, func(t *testing.T) {
|
|
require := require.New(t)
|
|
path, err := filepath.Abs(filepath.Join("./testdata", tc.File))
|
|
require.NoError(err)
|
|
|
|
actual, err := ParseConfigFile(path)
|
|
require.NoError(err)
|
|
|
|
// ParseConfig used to re-merge defaults for these three objects,
|
|
// despite them already being merged in LoadConfig. The test structs
|
|
// expect these defaults to be set, but not the DefaultConfig
|
|
// defaults, which include additional settings
|
|
oldDefault := &Config{
|
|
Consul: config.DefaultConsulConfig(),
|
|
Vault: config.DefaultVaultConfig(),
|
|
Autopilot: config.DefaultAutopilotConfig(),
|
|
}
|
|
actual = oldDefault.Merge(actual)
|
|
|
|
require.EqualValues(tc.Result, removeHelperAttributes(actual))
|
|
})
|
|
}
|
|
}
|
|
|
|
// In order to compare the Config struct after parsing, and from generating what
|
|
// is expected in the test, we need to remove helper attributes that are
|
|
// instantiated in the process of parsing the configuration
|
|
func removeHelperAttributes(c *Config) *Config {
|
|
if c.TLSConfig != nil {
|
|
c.TLSConfig.KeyLoader = nil
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (c *Config) addDefaults() {
|
|
if c.Client == nil {
|
|
c.Client = &ClientConfig{}
|
|
}
|
|
if c.Client.ServerJoin == nil {
|
|
c.Client.ServerJoin = &ServerJoin{}
|
|
}
|
|
if c.ACL == nil {
|
|
c.ACL = &ACLConfig{}
|
|
}
|
|
if c.Audit == nil {
|
|
c.Audit = &config.AuditConfig{}
|
|
}
|
|
if c.Consul == nil {
|
|
c.Consul = config.DefaultConsulConfig()
|
|
}
|
|
if c.Autopilot == nil {
|
|
c.Autopilot = config.DefaultAutopilotConfig()
|
|
}
|
|
if c.Vault == nil {
|
|
c.Vault = config.DefaultVaultConfig()
|
|
}
|
|
if c.Telemetry == nil {
|
|
c.Telemetry = &Telemetry{}
|
|
}
|
|
if c.Server == nil {
|
|
c.Server = &ServerConfig{}
|
|
}
|
|
if c.Server.ServerJoin == nil {
|
|
c.Server.ServerJoin = &ServerJoin{}
|
|
}
|
|
if c.Server.PlanRejectionTracker == nil {
|
|
c.Server.PlanRejectionTracker = &PlanRejectionTracker{}
|
|
}
|
|
}
|
|
|
|
// Tests for a panic parsing json with an object of exactly
|
|
// length 1 described in
|
|
// https://github.com/hashicorp/nomad/issues/1290
|
|
func TestConfig_ParsePanic(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c, err := ParseConfigFile("./testdata/obj-len-one.hcl")
|
|
if err != nil {
|
|
t.Fatalf("parse error: %s\n", err)
|
|
}
|
|
|
|
d, err := ParseConfigFile("./testdata/obj-len-one.json")
|
|
if err != nil {
|
|
t.Fatalf("parse error: %s\n", err)
|
|
}
|
|
|
|
require.EqualValues(t, c, d)
|
|
}
|
|
|
|
// Top level keys left by hcl when parsing slices in the config
|
|
// structure should not be unexpected
|
|
func TestConfig_ParseSliceExtra(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c, err := ParseConfigFile("./testdata/config-slices.json")
|
|
require.NoError(t, err)
|
|
|
|
opt := map[string]string{"o0": "foo", "o1": "bar"}
|
|
meta := map[string]string{"m0": "foo", "m1": "bar", "m2": "true", "m3": "1.2"}
|
|
env := map[string]string{"e0": "baz"}
|
|
srv := []string{"foo", "bar"}
|
|
|
|
require.EqualValues(t, opt, c.Client.Options)
|
|
require.EqualValues(t, meta, c.Client.Meta)
|
|
require.EqualValues(t, env, c.Client.ChrootEnv)
|
|
require.EqualValues(t, srv, c.Client.Servers)
|
|
require.EqualValues(t, srv, c.Server.EnabledSchedulers)
|
|
require.EqualValues(t, srv, c.Server.StartJoin)
|
|
require.EqualValues(t, srv, c.Server.RetryJoin)
|
|
|
|
// the alt format is also accepted by hcl as valid config data
|
|
c, err = ParseConfigFile("./testdata/config-slices-alt.json")
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, opt, c.Client.Options)
|
|
require.EqualValues(t, meta, c.Client.Meta)
|
|
require.EqualValues(t, env, c.Client.ChrootEnv)
|
|
require.EqualValues(t, srv, c.Client.Servers)
|
|
require.EqualValues(t, srv, c.Server.EnabledSchedulers)
|
|
require.EqualValues(t, srv, c.Server.StartJoin)
|
|
require.EqualValues(t, srv, c.Server.RetryJoin)
|
|
|
|
// small files keep more extra keys than large ones
|
|
_, err = ParseConfigFile("./testdata/obj-len-one-server.json")
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
var sample0 = &Config{
|
|
Region: "global",
|
|
Datacenter: "dc1",
|
|
DataDir: "/opt/data/nomad/data",
|
|
LogLevel: "INFO",
|
|
BindAddr: "0.0.0.0",
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "host.example.com",
|
|
RPC: "host.example.com",
|
|
Serf: "host.example.com",
|
|
},
|
|
Client: &ClientConfig{ServerJoin: &ServerJoin{}},
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
BootstrapExpect: 3,
|
|
RetryJoin: []string{"10.0.0.101", "10.0.0.102", "10.0.0.103"},
|
|
EncryptKey: "sHck3WL6cxuhuY7Mso9BHA==",
|
|
ServerJoin: &ServerJoin{},
|
|
PlanRejectionTracker: &PlanRejectionTracker{
|
|
NodeThreshold: 100,
|
|
NodeWindow: 31 * time.Minute,
|
|
NodeWindowHCL: "31m",
|
|
},
|
|
},
|
|
ACL: &ACLConfig{
|
|
Enabled: true,
|
|
},
|
|
Audit: &config.AuditConfig{
|
|
Enabled: pointer.Of(true),
|
|
Sinks: []*config.AuditSink{
|
|
{
|
|
DeliveryGuarantee: "enforced",
|
|
Name: "file",
|
|
Type: "file",
|
|
Format: "json",
|
|
Path: "/opt/nomad/audit.log",
|
|
RotateDuration: 24 * time.Hour,
|
|
RotateDurationHCL: "24h",
|
|
RotateBytes: 100,
|
|
RotateMaxFiles: 10,
|
|
},
|
|
},
|
|
Filters: []*config.AuditFilter{
|
|
{
|
|
Name: "default",
|
|
Type: "HTTPEvent",
|
|
Endpoints: []string{"/v1/metrics"},
|
|
Stages: []string{"*"},
|
|
Operations: []string{"*"},
|
|
},
|
|
},
|
|
},
|
|
Telemetry: &Telemetry{
|
|
PrometheusMetrics: true,
|
|
DisableHostname: true,
|
|
CollectionInterval: "60s",
|
|
collectionInterval: 60 * time.Second,
|
|
PublishAllocationMetrics: true,
|
|
PublishNodeMetrics: true,
|
|
},
|
|
LeaveOnInt: true,
|
|
LeaveOnTerm: true,
|
|
EnableSyslog: true,
|
|
SyslogFacility: "LOCAL0",
|
|
Consul: &config.ConsulConfig{
|
|
Token: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
|
ServerAutoJoin: pointer.Of(false),
|
|
ClientAutoJoin: pointer.Of(false),
|
|
},
|
|
Vault: &config.VaultConfig{
|
|
Enabled: pointer.Of(true),
|
|
Role: "nomad-cluster",
|
|
Addr: "http://host.example.com:8200",
|
|
},
|
|
TLSConfig: &config.TLSConfig{
|
|
EnableHTTP: true,
|
|
EnableRPC: true,
|
|
VerifyServerHostname: true,
|
|
CAFile: "/opt/data/nomad/certs/nomad-ca.pem",
|
|
CertFile: "/opt/data/nomad/certs/server.pem",
|
|
KeyFile: "/opt/data/nomad/certs/server-key.pem",
|
|
},
|
|
Autopilot: &config.AutopilotConfig{
|
|
CleanupDeadServers: pointer.Of(true),
|
|
},
|
|
}
|
|
|
|
func TestConfig_ParseSample0(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c, err := ParseConfigFile("./testdata/sample0.json")
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, sample0, c)
|
|
}
|
|
|
|
var sample1 = &Config{
|
|
Region: "global",
|
|
Datacenter: "dc1",
|
|
DataDir: "/opt/data/nomad/data",
|
|
LogLevel: "INFO",
|
|
BindAddr: "0.0.0.0",
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "host.example.com",
|
|
RPC: "host.example.com",
|
|
Serf: "host.example.com",
|
|
},
|
|
Client: &ClientConfig{ServerJoin: &ServerJoin{}},
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
BootstrapExpect: 3,
|
|
RetryJoin: []string{"10.0.0.101", "10.0.0.102", "10.0.0.103"},
|
|
EncryptKey: "sHck3WL6cxuhuY7Mso9BHA==",
|
|
ServerJoin: &ServerJoin{},
|
|
PlanRejectionTracker: &PlanRejectionTracker{
|
|
NodeThreshold: 100,
|
|
NodeWindow: 31 * time.Minute,
|
|
NodeWindowHCL: "31m",
|
|
},
|
|
},
|
|
ACL: &ACLConfig{
|
|
Enabled: true,
|
|
},
|
|
Audit: &config.AuditConfig{
|
|
Enabled: pointer.Of(true),
|
|
Sinks: []*config.AuditSink{
|
|
{
|
|
Name: "file",
|
|
Type: "file",
|
|
DeliveryGuarantee: "enforced",
|
|
Format: "json",
|
|
Path: "/opt/nomad/audit.log",
|
|
RotateDuration: 24 * time.Hour,
|
|
RotateDurationHCL: "24h",
|
|
RotateBytes: 100,
|
|
RotateMaxFiles: 10,
|
|
},
|
|
},
|
|
Filters: []*config.AuditFilter{
|
|
{
|
|
Name: "default",
|
|
Type: "HTTPEvent",
|
|
Endpoints: []string{"/v1/metrics"},
|
|
Stages: []string{"*"},
|
|
Operations: []string{"*"},
|
|
},
|
|
},
|
|
},
|
|
Telemetry: &Telemetry{
|
|
PrometheusMetrics: true,
|
|
DisableHostname: true,
|
|
CollectionInterval: "60s",
|
|
collectionInterval: 60 * time.Second,
|
|
PublishAllocationMetrics: true,
|
|
PublishNodeMetrics: true,
|
|
},
|
|
LeaveOnInt: true,
|
|
LeaveOnTerm: true,
|
|
EnableSyslog: true,
|
|
SyslogFacility: "LOCAL0",
|
|
Consul: &config.ConsulConfig{
|
|
EnableSSL: pointer.Of(true),
|
|
Token: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
|
ServerAutoJoin: pointer.Of(false),
|
|
ClientAutoJoin: pointer.Of(false),
|
|
},
|
|
Vault: &config.VaultConfig{
|
|
Enabled: pointer.Of(true),
|
|
Role: "nomad-cluster",
|
|
Addr: "http://host.example.com:8200",
|
|
},
|
|
TLSConfig: &config.TLSConfig{
|
|
EnableHTTP: true,
|
|
EnableRPC: true,
|
|
VerifyServerHostname: true,
|
|
CAFile: "/opt/data/nomad/certs/nomad-ca.pem",
|
|
CertFile: "/opt/data/nomad/certs/server.pem",
|
|
KeyFile: "/opt/data/nomad/certs/server-key.pem",
|
|
},
|
|
Autopilot: &config.AutopilotConfig{
|
|
CleanupDeadServers: pointer.Of(true),
|
|
},
|
|
}
|
|
|
|
func TestConfig_ParseDir(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c, err := LoadConfig("./testdata/sample1")
|
|
require.NoError(t, err)
|
|
|
|
// LoadConfig Merges all the config files in testdata/sample1, which makes empty
|
|
// maps & slices rather than nil, so set those
|
|
require.Empty(t, c.Client.Options)
|
|
c.Client.Options = nil
|
|
require.Empty(t, c.Client.Meta)
|
|
c.Client.Meta = nil
|
|
require.Empty(t, c.Client.ChrootEnv)
|
|
c.Client.ChrootEnv = nil
|
|
require.Empty(t, c.Server.StartJoin)
|
|
c.Server.StartJoin = nil
|
|
require.Empty(t, c.HTTPAPIResponseHeaders)
|
|
c.HTTPAPIResponseHeaders = nil
|
|
|
|
// LoadDir lists the config files
|
|
expectedFiles := []string{
|
|
"testdata/sample1/sample0.json",
|
|
"testdata/sample1/sample1.json",
|
|
"testdata/sample1/sample2.hcl",
|
|
}
|
|
require.Equal(t, expectedFiles, c.Files)
|
|
c.Files = nil
|
|
|
|
require.EqualValues(t, sample1, c)
|
|
}
|
|
|
|
// TestConfig_ParseDir_Matches_IndividualParsing asserts
|
|
// that parsing a directory config is the equivalent of
|
|
// parsing individual files in any order
|
|
func TestConfig_ParseDir_Matches_IndividualParsing(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
dirConfig, err := LoadConfig("./testdata/sample1")
|
|
require.NoError(t, err)
|
|
|
|
dirConfig = DefaultConfig().Merge(dirConfig)
|
|
|
|
files := []string{
|
|
"testdata/sample1/sample0.json",
|
|
"testdata/sample1/sample1.json",
|
|
"testdata/sample1/sample2.hcl",
|
|
}
|
|
|
|
for _, perm := range permutations(files) {
|
|
t.Run(fmt.Sprintf("permutation %v", perm), func(t *testing.T) {
|
|
config := DefaultConfig()
|
|
|
|
for _, f := range perm {
|
|
fc, err := LoadConfig(f)
|
|
require.NoError(t, err)
|
|
|
|
config = config.Merge(fc)
|
|
}
|
|
|
|
// sort files to get stable view
|
|
sort.Strings(config.Files)
|
|
sort.Strings(dirConfig.Files)
|
|
|
|
require.EqualValues(t, dirConfig, config)
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
// https://stackoverflow.com/a/30226442
|
|
func permutations(arr []string) [][]string {
|
|
var helper func([]string, int)
|
|
res := [][]string{}
|
|
|
|
helper = func(arr []string, n int) {
|
|
if n == 1 {
|
|
tmp := make([]string, len(arr))
|
|
copy(tmp, arr)
|
|
res = append(res, tmp)
|
|
} else {
|
|
for i := 0; i < n; i++ {
|
|
helper(arr, n-1)
|
|
if n%2 == 1 {
|
|
tmp := arr[i]
|
|
arr[i] = arr[n-1]
|
|
arr[n-1] = tmp
|
|
} else {
|
|
tmp := arr[0]
|
|
arr[0] = arr[n-1]
|
|
arr[n-1] = tmp
|
|
}
|
|
}
|
|
}
|
|
}
|
|
helper(arr, len(arr))
|
|
return res
|
|
}
|