diff --git a/command/agent/agent.go b/command/agent/agent.go index 1b5c19e8cb60..5c8399270d1a 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -45,13 +45,9 @@ type Agent struct { // consulSyncer registers the Nomad agent with the Consul Agent consulSyncer *consul.Syncer - client *client.Client - clientHTTPAddr string + client *client.Client - server *nomad.Server - serverHTTPAddr string - serverRPCAddr string - serverSerfAddr string + server *nomad.Server shutdown bool shutdownCh chan struct{} @@ -135,13 +131,13 @@ func (a *Agent) serverConfig() (*nomad.Config, error) { } // Set up the bind addresses - rpcAddr, err := a.getRPCAddr(true) + rpcAddr, err := net.ResolveTCPAddr("tcp", a.config.normalizedAddrs.RPC) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to parse RPC address %q: %v", a.config.normalizedAddrs.RPC, err) } - serfAddr, err := a.getSerfAddr(true) + serfAddr, err := net.ResolveTCPAddr("tcp", a.config.normalizedAddrs.Serf) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to parse Serf address %q: %v", a.config.normalizedAddrs.Serf, err) } conf.RPCAddr.Port = rpcAddr.Port conf.RPCAddr.IP = rpcAddr.IP @@ -149,21 +145,14 @@ func (a *Agent) serverConfig() (*nomad.Config, error) { conf.SerfConfig.MemberlistConfig.BindAddr = serfAddr.IP.String() // Set up the advertise addresses - httpAddr, err := a.getHTTPAddr(false) + rpcAddr, err = net.ResolveTCPAddr("tcp", a.config.AdvertiseAddrs.RPC) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to parse RPC advertise address %q: %v", a.config.AdvertiseAddrs.RPC, err) } - rpcAddr, err = a.getRPCAddr(false) + serfAddr, err = net.ResolveTCPAddr("tcp", a.config.AdvertiseAddrs.Serf) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to parse Serf advertise address %q: %v", a.config.AdvertiseAddrs.Serf, err) } - serfAddr, err = a.getSerfAddr(false) - if err != nil { - return nil, err - } - a.serverHTTPAddr = net.JoinHostPort(httpAddr.IP.String(), strconv.Itoa(httpAddr.Port)) - a.serverRPCAddr = net.JoinHostPort(rpcAddr.IP.String(), strconv.Itoa(rpcAddr.Port)) - a.serverSerfAddr = net.JoinHostPort(serfAddr.IP.String(), strconv.Itoa(serfAddr.Port)) conf.RPCAdvertise = rpcAddr conf.SerfConfig.MemberlistConfig.AdvertiseAddr = serfAddr.IP.String() conf.SerfConfig.MemberlistConfig.AdvertisePort = serfAddr.Port @@ -267,12 +256,7 @@ func (a *Agent) clientConfig() (*clientconfig.Config, error) { conf.Node.NodeClass = a.config.Client.NodeClass // Set up the HTTP advertise address - httpAddr, err := a.selectAddr(a.getHTTPAddr, false) - if err != nil { - return nil, err - } - conf.Node.HTTPAddr = httpAddr - a.clientHTTPAddr = httpAddr + conf.Node.HTTPAddr = a.config.AdvertiseAddrs.HTTP // Reserve resources on the node. r := conf.Node.Reserved @@ -330,18 +314,14 @@ func (a *Agent) setupServer() error { } a.server = server - // Resolve consul check addresses. Always use advertise address for services - httpCheckAddr, err := a.selectAddr(a.getHTTPAddr, !a.config.Consul.ChecksUseAdvertise) - if err != nil { - return err - } - rpcCheckAddr, err := a.selectAddr(a.getRPCAddr, !a.config.Consul.ChecksUseAdvertise) - if err != nil { - return err - } - serfCheckAddr, err := a.selectAddr(a.getSerfAddr, !a.config.Consul.ChecksUseAdvertise) - if err != nil { - return err + // Consul check addresses default to bind but can be toggled to use advertise + httpCheckAddr := a.config.normalizedAddrs.HTTP + rpcCheckAddr := a.config.normalizedAddrs.RPC + serfCheckAddr := a.config.normalizedAddrs.Serf + if a.config.Consul.ChecksUseAdvertise { + httpCheckAddr = a.config.AdvertiseAddrs.HTTP + rpcCheckAddr = a.config.AdvertiseAddrs.RPC + serfCheckAddr = a.config.AdvertiseAddrs.Serf } // Create the Nomad Server services for Consul @@ -349,7 +329,7 @@ func (a *Agent) setupServer() error { if a.config.Consul.AutoAdvertise { httpServ := &structs.Service{ Name: a.config.Consul.ServerServiceName, - PortLabel: a.serverHTTPAddr, + PortLabel: a.config.AdvertiseAddrs.HTTP, Tags: []string{consul.ServiceTagHTTP}, Checks: []*structs.ServiceCheck{ &structs.ServiceCheck{ @@ -365,7 +345,7 @@ func (a *Agent) setupServer() error { } rpcServ := &structs.Service{ Name: a.config.Consul.ServerServiceName, - PortLabel: a.serverRPCAddr, + PortLabel: a.config.AdvertiseAddrs.RPC, Tags: []string{consul.ServiceTagRPC}, Checks: []*structs.ServiceCheck{ &structs.ServiceCheck{ @@ -378,8 +358,8 @@ func (a *Agent) setupServer() error { }, } serfServ := &structs.Service{ - PortLabel: a.serverSerfAddr, Name: a.config.Consul.ServerServiceName, + PortLabel: a.config.AdvertiseAddrs.Serf, Tags: []string{consul.ServiceTagSerf}, Checks: []*structs.ServiceCheck{ &structs.ServiceCheck{ @@ -458,9 +438,9 @@ func (a *Agent) setupClient() error { a.client = client // Resolve the http check address - httpCheckAddr, err := a.selectAddr(a.getHTTPAddr, !a.config.Consul.ChecksUseAdvertise) - if err != nil { - return err + httpCheckAddr := a.config.normalizedAddrs.HTTP + if a.config.Consul.ChecksUseAdvertise { + httpCheckAddr = a.config.AdvertiseAddrs.HTTP } // Create the Nomad Client services for Consul @@ -469,7 +449,7 @@ func (a *Agent) setupClient() error { if a.config.Consul.AutoAdvertise { httpServ := &structs.Service{ Name: a.config.Consul.ClientServiceName, - PortLabel: a.clientHTTPAddr, + PortLabel: a.config.AdvertiseAddrs.HTTP, Tags: []string{consul.ServiceTagHTTP}, Checks: []*structs.ServiceCheck{ &structs.ServiceCheck{ @@ -493,103 +473,6 @@ func (a *Agent) setupClient() error { return nil } -// Defines the selector interface -type addrSelector func(bool) (*net.TCPAddr, error) - -// selectAddr returns the right address given a selector, and return it as a PortLabel -// preferBind is a weak preference, and will skip 0.0.0.0 -func (a *Agent) selectAddr(selector addrSelector, preferBind bool) (string, error) { - addr, err := selector(preferBind) - if err != nil { - return "", err - } - - if preferBind && addr.IP.String() == "0.0.0.0" { - addr, err = selector(false) - if err != nil { - return "", err - } - } - - address := net.JoinHostPort(addr.IP.String(), strconv.Itoa(addr.Port)) - return address, nil -} - -// getHTTPAddr returns the HTTP address to use based on the clients -// configuration. If bind is true, an address appropriate for binding is -// returned, otherwise an address for advertising is returned. Skip 0.0.0.0 -// unless returning a bind address, since that's the only time it's useful. -func (a *Agent) getHTTPAddr(bind bool) (*net.TCPAddr, error) { - advertAddr := a.config.AdvertiseAddrs.HTTP - bindAddr := a.config.Addresses.HTTP - globalBindAddr := a.config.BindAddr - port := a.config.Ports.HTTP - return pickAddress(bind, globalBindAddr, advertAddr, bindAddr, port, "HTTP") -} - -// getRPCAddr returns the HTTP address to use based on the clients -// configuration. If bind is true, an address appropriate for binding is -// returned, otherwise an address for advertising is returned. Skip 0.0.0.0 -// unless returning a bind address, since that's the only time it's useful. -func (a *Agent) getRPCAddr(bind bool) (*net.TCPAddr, error) { - advertAddr := a.config.AdvertiseAddrs.RPC - bindAddr := a.config.Addresses.RPC - globalBindAddr := a.config.BindAddr - port := a.config.Ports.RPC - return pickAddress(bind, globalBindAddr, advertAddr, bindAddr, port, "RPC") -} - -// getSerfAddr returns the Serf address to use based on the clients -// configuration. If bind is true, an address appropriate for binding is -// returned, otherwise an address for advertising is returned. Skip 0.0.0.0 -// unless returning a bind address, since that's the only time it's useful. -func (a *Agent) getSerfAddr(bind bool) (*net.TCPAddr, error) { - advertAddr := a.config.AdvertiseAddrs.Serf - bindAddr := a.config.Addresses.Serf - globalBindAddr := a.config.BindAddr - port := a.config.Ports.Serf - return pickAddress(bind, globalBindAddr, advertAddr, bindAddr, port, "Serf") -} - -// pickAddress is a shared helper to pick the address to either bind to or -// advertise. -func pickAddress(bind bool, globalBindAddr, advertiseAddr, bindAddr string, port int, service string) (*net.TCPAddr, error) { - var serverAddr string - if advertiseAddr != "" && !bind { - serverAddr = advertiseAddr - - // Check if the advertise has a port - if host, pport, err := net.SplitHostPort(advertiseAddr); err == nil { - if parsed, err := strconv.Atoi(pport); err == nil { - serverAddr = host - port = parsed - } - } - } else if bindAddr != "" && !(bindAddr == "0.0.0.0" && !bind) { - serverAddr = bindAddr - } else if globalBindAddr != "" && !(globalBindAddr == "0.0.0.0" && !bind) { - serverAddr = globalBindAddr - } else { - serverAddr = "127.0.0.1" - } - - ip := net.ParseIP(serverAddr) - if ip == nil { - joined := net.JoinHostPort(serverAddr, strconv.Itoa(port)) - addr, err := net.ResolveTCPAddr("tcp", joined) - if err == nil { - return addr, nil - } - - return nil, fmt.Errorf("Failed to parse %s %q as IP and failed to resolve address: %v", service, serverAddr, err) - } - - return &net.TCPAddr{ - IP: ip, - Port: port, - }, nil -} - // reservePortsForClient reserves a range of ports for the client to use when // it creates various plugins for log collection, executors, drivers, etc func (a *Agent) reservePortsForClient(conf *clientconfig.Config) error { diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index d4ba5bcdd37f..71283dcef02f 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -72,6 +72,9 @@ func makeAgent(t testing.TB, cb func(*Config)) (string, *Agent) { cb(conf) } + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } agent, err := NewAgent(conf, os.Stderr) if err != nil { os.RemoveAll(dir) @@ -93,26 +96,17 @@ func TestAgent_RPCPing(t *testing.T) { func TestAgent_ServerConfig(t *testing.T) { conf := DefaultConfig() + conf.DevMode = true // allow localhost for advertise addrs a := &Agent{config: conf} - // Returns error on bad serf addr - conf.AdvertiseAddrs.Serf = "nope" - _, err := a.serverConfig() - if err == nil || !strings.Contains(err.Error(), "Failed to parse Serf") { - t.Fatalf("expected serf address error, got: %#v", err) - } conf.AdvertiseAddrs.Serf = "127.0.0.1:4000" - - // Returns error on bad rpc addr - conf.AdvertiseAddrs.RPC = "nope" - _, err = a.serverConfig() - if err == nil || !strings.Contains(err.Error(), "Failed to parse RPC") { - t.Fatalf("expected rpc address error, got: %#v", err) - } conf.AdvertiseAddrs.RPC = "127.0.0.1:4001" conf.AdvertiseAddrs.HTTP = "10.10.11.1:4005" // Parses the advertise addrs correctly + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } out, err := a.serverConfig() if err != nil { t.Fatalf("err: %s", err) @@ -125,20 +119,27 @@ func TestAgent_ServerConfig(t *testing.T) { if serfPort != 4000 { t.Fatalf("expected 4000, got: %d", serfPort) } - if addr := out.RPCAdvertise; addr.IP.String() != "127.0.0.1" || addr.Port != 4001 { + + // Assert addresses weren't changed + if addr := conf.AdvertiseAddrs.RPC; addr != "127.0.0.1:4001" { t.Fatalf("bad rpc advertise addr: %#v", addr) } - if addr := a.serverHTTPAddr; addr != "10.10.11.1:4005" { + if addr := conf.AdvertiseAddrs.HTTP; addr != "10.10.11.1:4005" { t.Fatalf("expect 10.11.11.1:4005, got: %v", addr) } - if addr := a.serverRPCAddr; addr != "127.0.0.1:4001" { - t.Fatalf("expect 127.0.0.1:4001, got: %v", addr) + if addr := conf.Addresses.RPC; addr != "0.0.0.0" { + t.Fatalf("expect 0.0.0.0, got: %v", addr) } // Sets up the ports properly + conf.Addresses.RPC = "" + conf.Addresses.Serf = "" conf.Ports.RPC = 4003 conf.Ports.Serf = 4004 + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } out, err = a.serverConfig() if err != nil { t.Fatalf("err: %s", err) @@ -152,93 +153,92 @@ func TestAgent_ServerConfig(t *testing.T) { // Prefers advertise over bind addr conf.BindAddr = "127.0.0.3" + conf.Addresses.HTTP = "127.0.0.2" conf.Addresses.RPC = "127.0.0.2" conf.Addresses.Serf = "127.0.0.2" - conf.Addresses.HTTP = "127.0.0.2" conf.AdvertiseAddrs.HTTP = "10.0.0.10" conf.AdvertiseAddrs.RPC = "" conf.AdvertiseAddrs.Serf = "10.0.0.12:4004" + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } out, err = a.serverConfig() + fmt.Println(conf.Addresses.RPC) if err != nil { t.Fatalf("err: %s", err) } if addr := out.RPCAddr.IP.String(); addr != "127.0.0.2" { t.Fatalf("expect 127.0.0.2, got: %s", addr) } + if port := out.RPCAddr.Port; port != 4003 { + t.Fatalf("expect 4647, got: %d", port) + } if addr := out.SerfConfig.MemberlistConfig.BindAddr; addr != "127.0.0.2" { t.Fatalf("expect 127.0.0.2, got: %s", addr) } - if addr := a.serverHTTPAddr; addr != "10.0.0.10:4646" { - t.Fatalf("expect 10.0.0.10:4646, got: %s", addr) - } - // NOTE: AdvertiseAddr > Addresses > BindAddr > Defaults - if addr := a.serverRPCAddr; addr != "127.0.0.2:4003" { - t.Fatalf("expect 127.0.0.2:4003, got: %s", addr) + if port := out.SerfConfig.MemberlistConfig.BindPort; port != 4004 { + t.Fatalf("expect 4648, got: %d", port) } - if addr := a.serverSerfAddr; addr != "10.0.0.12:4004" { - t.Fatalf("expect 10.0.0.12:4004, got: %s", addr) + if addr := conf.Addresses.HTTP; addr != "127.0.0.2" { + t.Fatalf("expect 127.0.0.2, got: %s", addr) } - - // It correctly identifies the bind and advertise address when requested - if addr, err := a.selectAddr(a.getHTTPAddr, true); addr != "127.0.0.2:4646" || err != nil { - t.Fatalf("expect 127.0.0.2:4646, got: %s", addr) + if addr := conf.Addresses.RPC; addr != "127.0.0.2" { + t.Fatalf("expect 127.0.0.2, got: %s", addr) } - - if addr, err := a.selectAddr(a.getHTTPAddr, false); addr != "10.0.0.10:4646" || err != nil { - t.Fatalf("expect 10.0.0.10:4646, got: %s", addr) + if addr := conf.Addresses.Serf; addr != "127.0.0.2" { + t.Fatalf("expect 10.0.0.12, got: %s", addr) } - - if addr, err := a.selectAddr(a.getRPCAddr, true); addr != "127.0.0.2:4003" || err != nil { - t.Fatalf("expect 127.0.0.2:4003, got: %s", addr) + if addr := conf.normalizedAddrs.HTTP; addr != "127.0.0.2:4646" { + t.Fatalf("expect 127.0.0.2:4646, got: %s", addr) } - - if addr, err := a.selectAddr(a.getRPCAddr, false); addr != "127.0.0.2:4003" || err != nil { + if addr := conf.normalizedAddrs.RPC; addr != "127.0.0.2:4003" { t.Fatalf("expect 127.0.0.2:4003, got: %s", addr) } - - if addr, err := a.selectAddr(a.getSerfAddr, true); addr != "127.0.0.2:4004" || err != nil { - t.Fatalf("expect 127.0.0.2:4004, got: %s", addr) - } - - if addr, err := a.selectAddr(a.getSerfAddr, false); addr != "10.0.0.12:4004" || err != nil { + if addr := conf.normalizedAddrs.Serf; addr != "127.0.0.2:4004" { t.Fatalf("expect 10.0.0.12:4004, got: %s", addr) } - - // We don't resolve 0.0.0.0 unless we're asking for bind - conf.Addresses.HTTP = "0.0.0.0" - conf.AdvertiseAddrs.HTTP = "" - if addr, err := a.getHTTPAddr(false); addr.IP.String() != "127.0.0.3" || err != nil { - t.Fatalf("expect 127.0.0.3, got: %s", addr.IP.String()) + if addr := conf.AdvertiseAddrs.HTTP; addr != "10.0.0.10:4646" { + t.Fatalf("expect 10.0.0.10:4646, got: %s", addr) } - - // We still get 0.0.0.0 when explicitly asking for bind - if addr, err := a.getHTTPAddr(true); addr.IP.String() != "0.0.0.0" || err != nil { - t.Fatalf("expect 0.0.0.0, got: %s", addr.IP.String()) + if addr := conf.AdvertiseAddrs.RPC; addr != "127.0.0.2:4003" { + t.Fatalf("expect 127.0.0.2:4003, got: %s", addr) } - - // selectAddr does not return 0.0.0.0 with preferBind - if addr, err := a.selectAddr(a.getHTTPAddr, true); addr != "127.0.0.3:4646" || err != nil { - t.Fatalf("expect 127.0.0.3:4646, got: %s", addr) + if addr := conf.AdvertiseAddrs.Serf; addr != "10.0.0.12:4004" { + t.Fatalf("expect 10.0.0.12:4004, got: %s", addr) } conf.Server.NodeGCThreshold = "42g" + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } out, err = a.serverConfig() if err == nil || !strings.Contains(err.Error(), "unknown unit") { t.Fatalf("expected unknown unit error, got: %#v", err) } + conf.Server.NodeGCThreshold = "10s" + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } out, err = a.serverConfig() if threshold := out.NodeGCThreshold; threshold != time.Second*10 { t.Fatalf("expect 10s, got: %s", threshold) } conf.Server.HeartbeatGrace = "42g" + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } out, err = a.serverConfig() if err == nil || !strings.Contains(err.Error(), "unknown unit") { t.Fatalf("expected unknown unit error, got: %#v", err) } + conf.Server.HeartbeatGrace = "37s" + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } out, err = a.serverConfig() if threshold := out.HeartbeatGrace; threshold != time.Second*37 { t.Fatalf("expect 37s, got: %s", threshold) @@ -254,6 +254,9 @@ func TestAgent_ServerConfig(t *testing.T) { conf.Ports.HTTP = 4646 conf.Ports.RPC = 4647 conf.Ports.Serf = 4648 + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } out, err = a.serverConfig() if err != nil { t.Fatalf("err: %s", err) @@ -264,13 +267,22 @@ func TestAgent_ServerConfig(t *testing.T) { if addr := out.SerfConfig.MemberlistConfig.BindAddr; addr != "127.0.0.3" { t.Fatalf("expect 127.0.0.3, got: %s", addr) } - if addr := a.serverHTTPAddr; addr != "127.0.0.3:4646" { + if addr := conf.Addresses.HTTP; addr != "127.0.0.3" { + t.Fatalf("expect 127.0.0.3, got: %s", addr) + } + if addr := conf.Addresses.RPC; addr != "127.0.0.3" { + t.Fatalf("expect 127.0.0.3, got: %s", addr) + } + if addr := conf.Addresses.Serf; addr != "127.0.0.3" { + t.Fatalf("expect 127.0.0.3, got: %s", addr) + } + if addr := conf.normalizedAddrs.HTTP; addr != "127.0.0.3:4646" { t.Fatalf("expect 127.0.0.3:4646, got: %s", addr) } - if addr := a.serverRPCAddr; addr != "127.0.0.3:4647" { + if addr := conf.normalizedAddrs.RPC; addr != "127.0.0.3:4647" { t.Fatalf("expect 127.0.0.3:4647, got: %s", addr) } - if addr := a.serverSerfAddr; addr != "127.0.0.3:4648" { + if addr := conf.normalizedAddrs.Serf; addr != "127.0.0.3:4648" { t.Fatalf("expect 127.0.0.3:4648, got: %s", addr) } @@ -302,11 +314,16 @@ func TestAgent_ServerConfig(t *testing.T) { func TestAgent_ClientConfig(t *testing.T) { conf := DefaultConfig() + // enabled just to allow using localhost for all addresses + conf.DevMode = true a := &Agent{config: conf} conf.Client.Enabled = true conf.Addresses.HTTP = "127.0.0.1" conf.Ports.HTTP = 5678 + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } c, err := a.clientConfig() if err != nil { t.Fatalf("got err: %v", err) @@ -318,10 +335,14 @@ func TestAgent_ClientConfig(t *testing.T) { } conf = DefaultConfig() + conf.DevMode = true a = &Agent{config: conf} conf.Client.Enabled = true conf.Addresses.HTTP = "127.0.0.1" + if err := conf.normalizeAddrs(); err != nil { + t.Fatalf("error normalizing config: %v", err) + } c, err = a.clientConfig() if err != nil { t.Fatalf("got err: %v", err) diff --git a/command/agent/command.go b/command/agent/command.go index 9b1348c94f4a..c0ce3b78fc73 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -201,6 +201,12 @@ func (c *Command) readConfig() *Config { config.Version = c.Version config.VersionPrerelease = c.VersionPrerelease + // Normalize binds, ports, addresses, and advertise + if err := config.normalizeAddrs(); err != nil { + c.Ui.Error(err.Error()) + return nil + } + if dev { // Skip validation for dev mode return config @@ -213,7 +219,7 @@ func (c *Command) readConfig() *Config { } keyfile := filepath.Join(config.DataDir, serfKeyring) if _, err := os.Stat(keyfile); err == nil { - c.Ui.Error("WARNING: keyring exists but -encrypt given, using keyring") + c.Ui.Warn("WARNING: keyring exists but -encrypt given, using keyring") } } diff --git a/command/agent/command_test.go b/command/agent/command_test.go index 9e6d750ef5fa..248ef118524a 100644 --- a/command/agent/command_test.go +++ b/command/agent/command_test.go @@ -59,6 +59,9 @@ func TestCommand_Args(t *testing.T) { ShutdownCh: shutdownCh, } + // To prevent test failures on hosts whose hostname resolves to + // a loopback address, we must append a bind address + tc.args = append(tc.args, "-bind=169.254.0.1") if code := cmd.Run(tc.args); code != 1 { t.Fatalf("args: %v\nexit: %d\n", tc.args, code) } diff --git a/command/agent/config.go b/command/agent/config.go index b98784690559..95f26b21a2a3 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -46,8 +46,13 @@ type Config struct { Ports *Ports `mapstructure:"ports"` // Addresses is used to override the network addresses we bind to. + // + // Use normalizedAddrs if you need the host+port to bind to. Addresses *Addresses `mapstructure:"addresses"` + // normalizedAddr is set to the Address+Port by normalizeAddrs() + normalizedAddrs *Addresses + // AdvertiseAddrs is used to control the addresses we advertise. AdvertiseAddrs *AdvertiseAddrs `mapstructure:"advertise"` @@ -335,8 +340,8 @@ type Telemetry struct { CirconusBrokerSelectTag string `mapstructure:"circonus_broker_select_tag"` } -// Ports is used to encapsulate the various ports we bind to for network -// services. If any are not specified then the defaults are used instead. +// Ports encapsulates the various ports we bind to for network services. If any +// are not specified then the defaults are used instead. type Ports struct { HTTP int `mapstructure:"http"` RPC int `mapstructure:"rpc"` @@ -352,8 +357,8 @@ type Addresses struct { } // AdvertiseAddrs is used to control the addresses we advertise out for -// different network services. Not all network services support an -// advertise address. All are optional and default to BindAddr. +// different network services. All are optional and default to BindAddr and +// their default Port. type AdvertiseAddrs struct { HTTP string `mapstructure:"http"` RPC string `mapstructure:"rpc"` @@ -431,6 +436,7 @@ func (r *Resources) ParseReserved() error { // DevConfig is a Config that is used for dev mode of Nomad. func DevConfig() *Config { conf := DefaultConfig() + conf.BindAddr = "127.0.0.1" conf.LogLevel = "DEBUG" conf.Client.Enabled = true conf.Server.Enabled = true @@ -459,7 +465,7 @@ func DefaultConfig() *Config { LogLevel: "INFO", Region: "global", Datacenter: "dc1", - BindAddr: "127.0.0.1", + BindAddr: "0.0.0.0", Ports: &Ports{ HTTP: 4646, RPC: 4647, @@ -657,6 +663,126 @@ func (c *Config) Merge(b *Config) *Config { return &result } +// normalizeAddrs normalizes Addresses and AdvertiseAddrs to always be +// initialized and have sane defaults. +func (c *Config) normalizeAddrs() error { + c.Addresses.HTTP = normalizeBind(c.Addresses.HTTP, c.BindAddr) + c.Addresses.RPC = normalizeBind(c.Addresses.RPC, c.BindAddr) + c.Addresses.Serf = normalizeBind(c.Addresses.Serf, c.BindAddr) + c.normalizedAddrs = &Addresses{ + HTTP: fmt.Sprintf("%s:%d", c.Addresses.HTTP, c.Ports.HTTP), + RPC: fmt.Sprintf("%s:%d", c.Addresses.RPC, c.Ports.RPC), + Serf: fmt.Sprintf("%s:%d", c.Addresses.Serf, c.Ports.Serf), + } + + addr, err := normalizeAdvertise(c.AdvertiseAddrs.HTTP, c.Addresses.HTTP, c.Ports.HTTP, c.DevMode) + if err != nil { + return fmt.Errorf("Failed to parse HTTP advertise address: %v", err) + } + c.AdvertiseAddrs.HTTP = addr + + addr, err = normalizeAdvertise(c.AdvertiseAddrs.RPC, c.Addresses.RPC, c.Ports.RPC, c.DevMode) + if err != nil { + return fmt.Errorf("Failed to parse RPC advertise address: %v", err) + } + c.AdvertiseAddrs.RPC = addr + + addr, err = normalizeAdvertise(c.AdvertiseAddrs.Serf, c.Addresses.Serf, c.Ports.Serf, c.DevMode) + if err != nil { + return fmt.Errorf("Failed to parse Serf advertise address: %v", err) + } + c.AdvertiseAddrs.Serf = addr + + return nil +} + +// normalizeBind returns a normalized bind address. +// +// If addr is set it is used, if not the default bind address is used. +func normalizeBind(addr, bind string) string { + if addr == "" { + return bind + } + return addr +} + +// normalizeAdvertise returns a normalized advertise address. +// +// If addr is set, it is used and the default port is appended if no port is +// set. +// +// If addr is not set and bind is a valid address, the returned string is the +// bind+port. +// +// If addr is not set and bind is not a valid advertise address, the hostname +// is resolved and returned with the port. +// +// Loopback is only considered a valid advertise address in dev mode. +func normalizeAdvertise(addr string, bind string, defport int, dev bool) (string, error) { + if addr != "" { + // Default to using manually configured address + _, _, err := net.SplitHostPort(addr) + if err != nil { + if !isMissingPort(err) { + return "", fmt.Errorf("Error parsing advertise address %q: %v", addr, err) + } + + // missing port, append the default + return fmt.Sprintf("%s:%d", addr, defport), nil + } + return addr, nil + } + + // Fallback to bind address first, and then try resolving the local hostname + ips, err := net.LookupIP(bind) + if err != nil { + return "", fmt.Errorf("Error resolving bind address %q: %v", bind, err) + } + + // Return the first unicast address + for _, ip := range ips { + if ip.IsLinkLocalUnicast() || ip.IsGlobalUnicast() { + return fmt.Sprintf("%s:%d", ip, defport), nil + } + if ip.IsLoopback() && dev { + // loopback is fine for dev mode + return fmt.Sprintf("%s:%d", ip, defport), nil + } + } + + // As a last resort resolve the hostname and use it if it's not + // localhost (as localhost is never a sensible default) + host, err := os.Hostname() + if err != nil { + return "", fmt.Errorf("Unable to get hostname to set advertise address: %v", err) + } + + ips, err = net.LookupIP(host) + if err != nil { + return "", fmt.Errorf("Error resolving hostname %q for advertise address: %v", host, err) + } + + // Return the first unicast address + for _, ip := range ips { + if ip.IsLinkLocalUnicast() || ip.IsGlobalUnicast() { + return fmt.Sprintf("%s:%d", ip, defport), nil + } + if ip.IsLoopback() && dev { + // loopback is fine for dev mode + return fmt.Sprintf("%s:%d", ip, defport), nil + } + } + return "", fmt.Errorf("No valid advertise addresses, please set `advertise` manually") +} + +// isMissingPort returns true if an error is a "missing port" error from +// net.SplitHostPort. +func isMissingPort(err error) bool { + // matches error const in net/ipsock.go + const missingPort = "missing port in address" + return err != nil && strings.HasPrefix(err.Error(), missingPort) +} + // Merge is used to merge two server configs together func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig { result := *a diff --git a/command/agent/config_test.go b/command/agent/config_test.go index 40c9eca2771f..2c8571fa815c 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -2,6 +2,7 @@ package agent import ( "io/ioutil" + "net" "os" "path/filepath" "reflect" @@ -541,3 +542,14 @@ func TestResources_ParseReserved(t *testing.T) { } } + +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) + } +} diff --git a/command/agent/http.go b/command/agent/http.go index 2d0c0ed2d7b1..90fbbcdbb00b 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -49,7 +49,7 @@ type HTTPServer struct { // NewHTTPServer starts new HTTP server over the agent func NewHTTPServer(agent *Agent, config *Config, logOutput io.Writer) (*HTTPServer, error) { // Start the listener - lnAddr, err := agent.getHTTPAddr(true) + lnAddr, err := net.ResolveTCPAddr("tcp", config.normalizedAddrs.HTTP) if err != nil { return nil, err }