mirror of https://github.com/status-im/consul.git
Print a helpful message re: duplicate addresses
IP sockets provide nice endpoints where the kernel will fail to bind and will error out saying socket already in use. UNIX sockets, however, don't enjoy this nice property when cleaning up stale sockets on listen. Given the number of addresses in Consul, provide operators with a helpful message that indicates the source of the reused address. Before this fix, it was possible for the HTTP socket to unlink the RPC socket, leading to confusing blocked behavior when running commands like `consul info`. ``` % cat tmp.config.json { "addresses": { "http": "unix:///tmp/.consul.sock", "rpc": "unix:///tmp/.consul.sock" }, "unix_sockets": { "mode": "0700" } } % consul agent -config-file tmp.config.json -advertise=127.0.0.1 -data-dir=/tmp/ ==> All listening endpoints must be unique: HTTP address already configured for RPC Exit 1 ```
This commit is contained in:
parent
a440433ac8
commit
7a814fce63
|
@ -192,6 +192,12 @@ func (c *Command) readConfig() *Config {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure all endpoints are unique
|
||||||
|
if ok, err := config.verifyUniqueListeners(); !ok {
|
||||||
|
c.Ui.Error(fmt.Sprintf("All listening endpoints must be unique: %s", err))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check the data dir for signs of an un-migrated Consul 0.5.x or older
|
// Check the data dir for signs of an un-migrated Consul 0.5.x or older
|
||||||
// server. Consul refuses to start if this is present to protect a server
|
// server. Consul refuses to start if this is present to protect a server
|
||||||
// with existing data from starting on a fresh data set.
|
// with existing data from starting on a fresh data set.
|
||||||
|
@ -301,6 +307,67 @@ func (c *Command) readConfig() *Config {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verifyUniqueListeners checks to see if an address was used more than once in
|
||||||
|
// the config
|
||||||
|
func (config *Config) verifyUniqueListeners() (bool, error) {
|
||||||
|
type key struct {
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
const numUniqueAddrs = 7
|
||||||
|
m := make(map[key]string, numUniqueAddrs)
|
||||||
|
|
||||||
|
testFunc := func(k key, descr string) (bool, error) {
|
||||||
|
if k.host == "" {
|
||||||
|
k.host = "0.0.0.0"
|
||||||
|
} else if strings.HasPrefix(k.host, "unix") {
|
||||||
|
// Don't compare ports on unix sockets
|
||||||
|
k.port = 0
|
||||||
|
}
|
||||||
|
if k.host == "0.0.0.0" && k.port <= 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := m[k]
|
||||||
|
if ok {
|
||||||
|
return false, fmt.Errorf("%s address already configured for %s", descr, v)
|
||||||
|
}
|
||||||
|
m[k] = descr
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := testFunc(key{config.Addresses.RPC, config.Ports.RPC}, "RPC"); !ok {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := testFunc(key{config.Addresses.DNS, config.Ports.DNS}, "DNS"); !ok {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := testFunc(key{config.Addresses.HTTP, config.Ports.HTTP}, "HTTP"); !ok {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := testFunc(key{config.Addresses.HTTPS, config.Ports.HTTPS}, "HTTPS"); !ok {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := testFunc(key{config.AdvertiseAddr, config.Ports.Server}, "Server RPC"); !ok {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := testFunc(key{config.AdvertiseAddr, config.Ports.SerfLan}, "Serf LAN"); !ok {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := testFunc(key{config.AdvertiseAddr, config.Ports.SerfWan}, "Serf WAN"); !ok {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// setupLoggers is used to setup the logGate, logWriter, and our logOutput
|
// setupLoggers is used to setup the logGate, logWriter, and our logOutput
|
||||||
func (c *Command) setupLoggers(config *Config) (*GatedWriter, *logWriter, io.Writer) {
|
func (c *Command) setupLoggers(config *Config) (*GatedWriter, *logWriter, io.Writer) {
|
||||||
// Setup logging. First create the gated log writer, which will
|
// Setup logging. First create the gated log writer, which will
|
||||||
|
|
|
@ -989,6 +989,42 @@ func TestDecodeConfig_Services(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecodeConfig_verifyUniqueListeners(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg string
|
||||||
|
pass bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"http_rpc1",
|
||||||
|
`{"addresses": {"http": "0.0.0.0", "rpc": "127.0.0.1"}, "ports": {"rpc": 8000, "dns": 8000}}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"http_rpc IP identical",
|
||||||
|
`{"addresses": {"http": "0.0.0.0", "rpc": "0.0.0.0"}, "ports": {"rpc": 8000, "dns": 8000}}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"http_rpc unix identical (diff ports)",
|
||||||
|
`{"addresses": {"http": "unix:///tmp/.consul.sock", "rpc": "unix:///tmp/.consul.sock"}, "ports": {"rpc": 8000, "dns": 8001}}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
config, err := DecodeConfig(bytes.NewReader([]byte(test.cfg)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s %s", test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := config.verifyUniqueListeners()
|
||||||
|
if ok != test.pass {
|
||||||
|
t.Errorf("err: %s should have %v: %v: %v", test.name, test.pass, test.cfg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDecodeConfig_Checks(t *testing.T) {
|
func TestDecodeConfig_Checks(t *testing.T) {
|
||||||
input := `{
|
input := `{
|
||||||
"checks": [
|
"checks": [
|
||||||
|
|
Loading…
Reference in New Issue