From faf5cc04477f9a4a0e92b1f3e44d98860025ceb6 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 9 Sep 2015 21:42:50 -0700 Subject: [PATCH 1/4] agent: config merge works + tests --- command/agent/config.go | 59 ++++++++++++++++++++++++++++++++---- command/agent/config_test.go | 45 +++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 command/agent/config_test.go diff --git a/command/agent/config.go b/command/agent/config.go index 08d6db27f..03bce2593 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -175,14 +175,61 @@ func DefaultConfig() *Config { } // Merge merges two configurations. -func (c *Config) Merge(c2 *Config) *Config { - result := new(Config) +func (a *Config) Merge(b *Config) *Config { + var result Config = *a - result.Telemetry = c.Telemetry - if c2.Telemetry != nil { - result.Telemetry = c2.Telemetry + if b.Region != "" { + result.Region = b.Region } - return result + if b.Datacenter != "" { + result.Datacenter = b.Datacenter + } + if b.NodeName != "" { + result.NodeName = b.NodeName + } + if b.DataDir != "" { + result.DataDir = b.DataDir + } + if b.LogLevel != "" { + result.LogLevel = b.LogLevel + } + if b.HttpAddr != "" { + result.HttpAddr = b.HttpAddr + } + if b.EnableDebug { + result.EnableDebug = true + } + // TODO: merge client config + if b.Client != nil { + result.Client = b.Client + } + // TODO: merge server config + if b.Server != nil { + result.Server = b.Server + } + // TODO: merge telemetry config + if b.Telemetry != nil { + result.Telemetry = b.Telemetry + } + if b.LeaveOnInt { + result.LeaveOnInt = true + } + if b.LeaveOnTerm { + result.LeaveOnTerm = true + } + if b.EnableSyslog { + result.EnableSyslog = true + } + if b.SyslogFacility != "" { + result.SyslogFacility = b.SyslogFacility + } + if b.DisableUpdateCheck { + result.DisableUpdateCheck = true + } + if b.DisableAnonymousSignature { + result.DisableAnonymousSignature = true + } + return &result } // LoadConfig loads the configuration at the given path, regardless if diff --git a/command/agent/config_test.go b/command/agent/config_test.go new file mode 100644 index 000000000..f58abcf4f --- /dev/null +++ b/command/agent/config_test.go @@ -0,0 +1,45 @@ +package agent + +import ( + "reflect" + "testing" +) + +func TestConfig_Merge(t *testing.T) { + c1 := &Config{ + Region: "region1", + Datacenter: "dc1", + NodeName: "node1", + DataDir: "/tmp/dir1", + LogLevel: "INFO", + HttpAddr: "127.0.0.1:4646", + EnableDebug: false, + LeaveOnInt: false, + LeaveOnTerm: false, + EnableSyslog: false, + SyslogFacility: "local0.info", + DisableUpdateCheck: false, + DisableAnonymousSignature: false, + } + + c2 := &Config{ + Region: "region2", + Datacenter: "dc2", + NodeName: "node2", + DataDir: "/tmp/dir2", + LogLevel: "DEBUG", + HttpAddr: "0.0.0.0:80", + EnableDebug: true, + LeaveOnInt: true, + LeaveOnTerm: true, + EnableSyslog: true, + SyslogFacility: "local0.debug", + DisableUpdateCheck: true, + DisableAnonymousSignature: true, + } + + result := c1.Merge(c2) + if !reflect.DeepEqual(result, c2) { + t.Fatalf("bad: %#v", result) + } +} From 79b4db11e0fa12c85cb09947fbd7a3f78180635b Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 9 Sep 2015 21:43:11 -0700 Subject: [PATCH 2/4] testutil: server uses dynamic ports --- testutil/server.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testutil/server.go b/testutil/server.go index 81ded4649..745cb9c3b 100644 --- a/testutil/server.go +++ b/testutil/server.go @@ -111,8 +111,7 @@ func NewTestServer(t *testing.T, cb ServerConfigCallback) *TestServer { } // Start the server - // TODO: Use "-config", configFile.Name() - cmd := exec.Command("nomad", "agent", "-dev") + cmd := exec.Command("nomad", "agent", "-dev", "-config", configFile.Name()) cmd.Stdout = stdout cmd.Stderr = stderr if err := cmd.Start(); err != nil { @@ -127,7 +126,7 @@ func NewTestServer(t *testing.T, cb ServerConfigCallback) *TestServer { PID: cmd.Process.Pid, t: t, - HTTPAddr: "127.0.0.1:4646", // TODO nomadConfig.HTTPAddr, + HTTPAddr: nomadConfig.HTTPAddr, HttpClient: client, } From bc683a800e97c69f98f02339e6375ba6dc648f69 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Wed, 9 Sep 2015 22:11:48 -0700 Subject: [PATCH 3/4] agent: config loading tests --- command/agent/config_test.go | 139 +++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/command/agent/config_test.go b/command/agent/config_test.go index f58abcf4f..b7cca140b 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -1,6 +1,9 @@ package agent import ( + "io/ioutil" + "os" + "path/filepath" "reflect" "testing" ) @@ -43,3 +46,139 @@ func TestConfig_Merge(t *testing.T) { t.Fatalf("bad: %#v", result) } } + +func TestConfig_LoadConfigFile(t *testing.T) { + // Fails if the file doesn't exist + if _, err := LoadConfigFile("/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 := LoadConfigFile(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 := LoadConfigFile(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) + + 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) + } + + 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) + } +} From b95ed91628d55fbe94d04bd7372cf1020c02ba70 Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Thu, 10 Sep 2015 11:24:59 -0700 Subject: [PATCH 4/4] agent: merge telemetry, server, and client configs --- command/agent/config.go | 122 +++++++++++++++++++++++++++++++---- command/agent/config_test.go | 51 ++++++++++++++- 2 files changed, 160 insertions(+), 13 deletions(-) diff --git a/command/agent/config.go b/command/agent/config.go index 03bce2593..b8c42169b 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -199,18 +199,6 @@ func (a *Config) Merge(b *Config) *Config { if b.EnableDebug { result.EnableDebug = true } - // TODO: merge client config - if b.Client != nil { - result.Client = b.Client - } - // TODO: merge server config - if b.Server != nil { - result.Server = b.Server - } - // TODO: merge telemetry config - if b.Telemetry != nil { - result.Telemetry = b.Telemetry - } if b.LeaveOnInt { result.LeaveOnInt = true } @@ -229,6 +217,116 @@ func (a *Config) Merge(b *Config) *Config { if b.DisableAnonymousSignature { result.DisableAnonymousSignature = true } + + // Apply the telemetry config + if result.Telemetry == nil && b.Telemetry != nil { + telemetry := *b.Telemetry + result.Telemetry = &telemetry + } else if b.Telemetry != nil { + result.Telemetry = result.Telemetry.Merge(b.Telemetry) + } + + // Apply the client config + if result.Client == nil && b.Client != nil { + client := *b.Client + result.Client = &client + } else if b.Client != nil { + result.Client = result.Client.Merge(b.Client) + } + + // Apply the server config + if result.Server == nil && b.Server != nil { + server := *b.Server + result.Server = &server + } else if b.Server != nil { + result.Server = result.Server.Merge(b.Server) + } + + return &result +} + +// Merge is used to merge two server configs together +func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig { + var result ServerConfig = *a + + if b.Enabled { + result.Enabled = true + } + if b.Bootstrap { + result.Bootstrap = true + } + if b.BootstrapExpect > 0 { + result.BootstrapExpect = b.BootstrapExpect + } + if b.DataDir != "" { + result.DataDir = b.DataDir + } + if b.ProtocolVersion != 0 { + result.ProtocolVersion = b.ProtocolVersion + } + if b.AdvertiseAddr != "" { + result.AdvertiseAddr = b.AdvertiseAddr + } + if b.BindAddr != "" { + result.BindAddr = b.BindAddr + } + if b.NumSchedulers != 0 { + result.NumSchedulers = b.NumSchedulers + } + + // Add the schedulers + result.EnabledSchedulers = append(result.EnabledSchedulers, b.EnabledSchedulers...) + + return &result +} + +// Merge is used to merge two client configs together +func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig { + var result ClientConfig = *a + + if b.Enabled { + result.Enabled = true + } + if b.StateDir != "" { + result.StateDir = b.StateDir + } + if b.AllocDir != "" { + result.AllocDir = b.AllocDir + } + if b.NodeID != "" { + result.NodeID = b.NodeID + } + if b.NodeClass != "" { + result.NodeClass = b.NodeClass + } + + // Add the servers + result.Servers = append(result.Servers, b.Servers...) + + // Add the meta map values + if result.Meta == nil { + result.Meta = make(map[string]string) + } + for k, v := range b.Meta { + result.Meta[k] = v + } + + return &result +} + +// Merge is used to merge two telemetry configs together +func (a *Telemetry) Merge(b *Telemetry) *Telemetry { + var result Telemetry = *a + + if b.StatsiteAddr != "" { + result.StatsiteAddr = b.StatsiteAddr + } + if b.StatsdAddr != "" { + result.StatsdAddr = b.StatsdAddr + } + if b.DisableHostname { + result.DisableHostname = true + } return &result } diff --git a/command/agent/config_test.go b/command/agent/config_test.go index b7cca140b..ccc765551 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -6,6 +6,8 @@ import ( "path/filepath" "reflect" "testing" + + "github.com/hashicorp/nomad/nomad/structs" ) func TestConfig_Merge(t *testing.T) { @@ -23,6 +25,28 @@ func TestConfig_Merge(t *testing.T) { SyslogFacility: "local0.info", DisableUpdateCheck: false, DisableAnonymousSignature: false, + 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", + NodeID: "node1", + NodeClass: "class1", + }, + Server: &ServerConfig{ + Enabled: false, + Bootstrap: false, + BootstrapExpect: 1, + DataDir: "/tmp/data1", + ProtocolVersion: 1, + AdvertiseAddr: "127.0.0.1:4647", + BindAddr: "127.0.0.1", + NumSchedulers: 1, + }, } c2 := &Config{ @@ -39,11 +63,36 @@ func TestConfig_Merge(t *testing.T) { SyslogFacility: "local0.debug", DisableUpdateCheck: true, DisableAnonymousSignature: true, + 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", + NodeID: "node2", + NodeClass: "class2", + Servers: []string{"server2"}, + Meta: map[string]string{"baz": "zip"}, + }, + Server: &ServerConfig{ + Enabled: true, + Bootstrap: true, + BootstrapExpect: 2, + DataDir: "/tmp/data2", + ProtocolVersion: 2, + AdvertiseAddr: "127.0.0.2:4647", + BindAddr: "127.0.0.2", + NumSchedulers: 2, + EnabledSchedulers: []string{structs.JobTypeBatch}, + }, } result := c1.Merge(c2) if !reflect.DeepEqual(result, c2) { - t.Fatalf("bad: %#v", result) + t.Fatalf("bad:\n%#v\n%#v", result.Server, c2.Server) } }