open-nomad/command/agent/config_test.go
Dara Kong 9b2b39c7d6 Fix "http_api_response_headers" config not working
On startup, the agent starts with either a dev or default config.  Extra
config files are merged into final config.

The HTTPAPIResponseHeaders config was missing from the merge logic
therefore it would always result in empty value (regardlesss of any
config files specified).
2016-05-16 20:57:54 -07:00

454 lines
10 KiB
Go

package agent
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/hashicorp/nomad/nomad/structs"
)
func TestConfig_Merge(t *testing.T) {
c1 := &Config{
Region: "global",
Datacenter: "dc1",
NodeName: "node1",
DataDir: "/tmp/dir1",
LogLevel: "INFO",
EnableDebug: false,
LeaveOnInt: false,
LeaveOnTerm: false,
EnableSyslog: false,
SyslogFacility: "local0.info",
DisableUpdateCheck: false,
DisableAnonymousSignature: false,
BindAddr: "127.0.0.1",
Telemetry: &Telemetry{
StatsiteAddr: "127.0.0.1:8125",
StatsdAddr: "127.0.0.1:8125",
DisableHostname: false,
},
Client: &ClientConfig{
Enabled: false,
StateDir: "/tmp/state1",
AllocDir: "/tmp/alloc1",
NodeClass: "class1",
Options: map[string]string{
"foo": "bar",
},
NetworkSpeed: 100,
MaxKillTimeout: "20s",
ClientMaxPort: 19996,
Reserved: &Resources{
CPU: 10,
MemoryMB: 10,
DiskMB: 10,
IOPS: 10,
ReservedPorts: "1,10-30,55",
ParsedReservedPorts: []int{1, 2, 4},
},
},
Server: &ServerConfig{
Enabled: false,
BootstrapExpect: 1,
DataDir: "/tmp/data1",
ProtocolVersion: 1,
NumSchedulers: 1,
NodeGCThreshold: "1h",
HeartbeatGrace: "30s",
},
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",
},
Atlas: &AtlasConfig{
Infrastructure: "hashicorp/test1",
Token: "abc",
Join: false,
Endpoint: "foo",
},
HTTPAPIResponseHeaders: map[string]string{
"Access-Control-Allow-Origin": "*",
},
}
c2 := &Config{
Region: "region2",
Datacenter: "dc2",
NodeName: "node2",
DataDir: "/tmp/dir2",
LogLevel: "DEBUG",
EnableDebug: true,
LeaveOnInt: true,
LeaveOnTerm: true,
EnableSyslog: true,
SyslogFacility: "local0.debug",
DisableUpdateCheck: true,
DisableAnonymousSignature: true,
BindAddr: "127.0.0.2",
Telemetry: &Telemetry{
StatsiteAddr: "127.0.0.2:8125",
StatsdAddr: "127.0.0.2:8125",
DisableHostname: true,
},
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",
},
ClientMaxPort: 20000,
ClientMinPort: 22000,
NetworkSpeed: 105,
MaxKillTimeout: "50s",
Reserved: &Resources{
CPU: 15,
MemoryMB: 15,
DiskMB: 15,
IOPS: 15,
ReservedPorts: "2,10-30,55",
ParsedReservedPorts: []int{1, 2, 3},
},
},
Server: &ServerConfig{
Enabled: true,
BootstrapExpect: 2,
DataDir: "/tmp/data2",
ProtocolVersion: 2,
NumSchedulers: 2,
EnabledSchedulers: []string{structs.JobTypeBatch},
NodeGCThreshold: "12h",
HeartbeatGrace: "2m",
RejoinAfterLeave: true,
StartJoin: []string{"1.1.1.1"},
RetryJoin: []string{"1.1.1.1"},
RetryInterval: "10s",
retryInterval: time.Second * 10,
},
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",
},
Atlas: &AtlasConfig{
Infrastructure: "hashicorp/test2",
Token: "xyz",
Join: true,
Endpoint: "bar",
},
HTTPAPIResponseHeaders: map[string]string{
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
},
}
result := c1.Merge(c2)
if !reflect.DeepEqual(result, c2) {
t.Fatalf("bad:\n%#v\n%#v", result, c2)
}
}
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
ln, err := config.Listener("tcp", "127.0.0.1", 24000)
if err != nil {
t.Fatalf("err: %s", err)
}
ln.Close()
if net := ln.Addr().Network(); net != "tcp" {
t.Fatalf("expected tcp, got: %q", net)
}
if addr := ln.Addr().String(); addr != "127.0.0.1:24000" {
t.Fatalf("expected 127.0.0.1:4646, got: %q", addr)
}
// Falls back to default bind address if non provided
config.BindAddr = "0.0.0.0"
ln, err = config.Listener("tcp4", "", 24000)
if err != nil {
t.Fatalf("err: %s", err)
}
ln.Close()
if addr := ln.Addr().String(); addr != "0.0.0.0:24000" {
t.Fatalf("expected 0.0.0.0:24000, got: %q", addr)
}
}
func TestResources_ParseReserved(t *testing.T) {
cases := []struct {
Input string
Parsed []int
Err bool
}{
{
"1,2,3",
[]int{1, 2, 3},
false,
},
{
"3,1,2,1,2,3,1-3",
[]int{1, 2, 3},
false,
},
{
"3-1",
nil,
true,
},
{
"1-3,2-4",
[]int{1, 2, 3, 4},
false,
},
{
"1-3,4,5-5,6,7,8-10",
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
false,
},
}
for i, tc := range cases {
r := &Resources{ReservedPorts: tc.Input}
err := r.ParseReserved()
if (err != nil) != tc.Err {
t.Fatalf("test case %d: %v", i, err)
continue
}
if !reflect.DeepEqual(r.ParsedReservedPorts, tc.Parsed) {
t.Fatalf("test case %d: \n\n%#v\n\n%#v", i, r.ParsedReservedPorts, tc.Parsed)
}
}
}