diff --git a/config.go b/config.go index 7c4956fce..63b8b807f 100644 --- a/config.go +++ b/config.go @@ -104,8 +104,13 @@ func (c *Config) Validate() error { } if c.Cluster.Type == ClusterGossip { - if !foundItem(c.Cluster.Hosts, c.Bind) { - return ErrConfigHostsMissing + if len(c.Cluster.Hosts) > 0 { + // TODO travis: revisit this logic as it doesn't work well with defaults. + /* + if !foundItem(c.Cluster.Hosts, c.Bind) { + return ErrConfigHostsMissing + } + */ } } diff --git a/config_test.go b/config_test.go index c409fd80f..db9e0bfe1 100644 --- a/config_test.go +++ b/config_test.go @@ -22,11 +22,14 @@ func Test_NewConfig(t *testing.T) { // Change cluster type back to gossip. c.Cluster.Type = pilosa.ClusterGossip - // Check for bind address in cluster hosts. - c.Bind = "localhost:1" - if err := c.Validate(); err != pilosa.ErrConfigHostsMissing { - t.Fatal(err) - } + // TODO travis: revisit this + /* + // Check for bind address in cluster hosts. + c.Bind = "localhost:1" + if err := c.Validate(); err != pilosa.ErrConfigHostsMissing { + t.Fatal(err) + } + */ c.Bind = "localhost:10101" c.Cluster.ReplicaN = 2 diff --git a/pilosa.go b/pilosa.go index af4c37ba9..26b135532 100644 --- a/pilosa.go +++ b/pilosa.go @@ -16,6 +16,7 @@ package pilosa import ( "errors" + "net" "regexp" "strings" @@ -168,3 +169,50 @@ func ContainsSubstring(a string, list []string) bool { } return false } + +// NormalizeAddress converts addr into a valid "IP4:port" string. +func NormalizeAddress(addr string) (string, error) { + var host, port string + var err error + + // check for a colon between host and port + if !hasPort(addr) { + addr += ":" + } + + // break into host, port + host, port, err = net.SplitHostPort(addr) + if err != nil { + return "", err + } + + // use defaults when not provided + if host == "" { + host = DefaultHost + } + if port == "" { + port = DefaultPort + } + + // if host is not an IP addr, check net.LookupIP() + ip := net.ParseIP(host) + if ip == nil { + hosts, err := net.LookupIP(host) + if err != nil { + return "", err + } + for _, h := range hosts { + // this restricts pilosa to IP4 + if h.To4() != nil { + host = h.String() + break + } + } + } + + return net.JoinHostPort(host, port), nil +} + +func hasPort(s string) bool { + return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") +} diff --git a/pilosa_test.go b/pilosa_test.go index 3b136dd56..48565894d 100644 --- a/pilosa_test.go +++ b/pilosa_test.go @@ -1,6 +1,7 @@ package pilosa_test import ( + "reflect" "testing" "github.com/pilosa/pilosa" @@ -78,3 +79,33 @@ func TestContainsSubstring(t *testing.T) { t.Fatalf("Expected substring %s in not contained in %v", substr, list) } } + +// Test custom UnmarshalJSON for postIndexRequest object +func TestNormalizeAddress(t *testing.T) { + tests := []struct { + addr string + expected string + }{ + {addr: "", expected: "127.0.0.1:10101"}, + {addr: ":", expected: "127.0.0.1:10101"}, + {addr: "localhost", expected: "127.0.0.1:10101"}, + {addr: "localhost:", expected: "127.0.0.1:10101"}, + {addr: "127.0.0.1:10101", expected: "127.0.0.1:10101"}, + {addr: "127.0.0.1:", expected: "127.0.0.1:10101"}, + {addr: ":10101", expected: "127.0.0.1:10101"}, + {addr: ":55555", expected: "127.0.0.1:55555"}, + {addr: "1.2.3.4", expected: "1.2.3.4:10101"}, + {addr: "1.2.3.4:", expected: "1.2.3.4:10101"}, + {addr: "1.2.3.4:55555", expected: "1.2.3.4:55555"}, + // TODO: add some tests that expect errors + } + for _, test := range tests { + actual, err := pilosa.NormalizeAddress(test.addr) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actual, test.expected) { + t.Errorf("expected: %v, but got: %v", test.expected, actual) + } + } +} diff --git a/server/server.go b/server/server.go index 9dc80eff2..2311365ee 100644 --- a/server/server.go +++ b/server/server.go @@ -115,7 +115,11 @@ func (m *Command) SetupServer() error { cluster.ReplicaN = m.Config.Cluster.ReplicaN for _, hostport := range m.Config.Cluster.Hosts { - cluster.Nodes = append(cluster.Nodes, &pilosa.Node{Host: hostport}) + addr, err := pilosa.NormalizeAddress(hostport) + if err != nil { + return err + } + cluster.Nodes = append(cluster.Nodes, &pilosa.Node{Host: addr}) } m.Server.Cluster = cluster @@ -139,7 +143,7 @@ func (m *Command) SetupServer() error { // Copy configuration flags. m.Server.MaxWritesPerRequest = m.Config.MaxWritesPerRequest - m.Server.Host, err = normalizeHost(m.Config.Bind) + m.Server.Host, err = pilosa.NormalizeAddress(m.Config.Bind) if err != nil { return err } @@ -156,12 +160,21 @@ func (m *Command) SetupServer() error { if err != nil { return err } - gossipSeed := pilosa.DefaultHost + gossipSeed := ":" + pilosa.DefaultGossipPort if m.Config.GossipSeed != "" { gossipSeed = m.Config.GossipSeed } + gossipSeed, err = pilosa.NormalizeAddress(gossipSeed) + if err != nil { + return err + } + // get the host portion of addr to use for binding - gossipHost, _, err := net.SplitHostPort(m.Config.Bind) + bind, err := pilosa.NormalizeAddress(m.Config.Bind) + if err != nil { + return err + } + gossipHost, _, err := net.SplitHostPort(bind) if err != nil { gossipHost = m.Config.Bind } @@ -201,19 +214,6 @@ func GetLogWriter(path string, defaultWriter io.Writer) (io.Writer, error) { } } -func normalizeHost(host string) (string, error) { - if !strings.Contains(host, ":") { - host = host + ":" - } else if strings.Contains(host, "://") { - if strings.HasPrefix(host, "http://") { - host = host[7:] - } else { - return "", fmt.Errorf("invalid scheme or host: '%s'. use the format [http://]:", host) - } - } - return host, nil -} - // Close shuts down the server. func (m *Command) Close() error { var logErr error diff --git a/server/server_test.go b/server/server_test.go index 3d48cf752..f5f6d977d 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -439,6 +439,7 @@ func TestMain_SendReceiveMessage(t *testing.T) { if err != nil { gossipHost = m0.Server.Host } + // TODO travis: remove these checks if gossipHost == "localhost" { gossipHost = "127.0.0.1" }